@dimina-kit/devtools 0.4.0-dev.20260625063832 → 0.4.0-dev.20260630033804
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/dist/main/app/app.js +2 -0
- package/dist/main/index.bundle.js +241 -151
- package/dist/main/ipc/bridge-router.d.ts +21 -0
- package/dist/main/ipc/bridge-router.js +63 -3
- package/dist/main/services/automation/index.js +0 -1
- package/dist/main/services/elements-forward/index.js +81 -72
- package/dist/main/services/mcp/target-manager.d.ts +3 -3
- package/dist/main/services/views/miniapp-partition.d.ts +18 -8
- package/dist/main/services/views/miniapp-partition.js +34 -13
- package/dist/main/services/views/view-manager.js +76 -41
- package/dist/main/services/workbench-coi-server.d.ts +7 -0
- package/dist/main/services/workbench-coi-server.js +6 -0
- package/dist/main/services/workbench-context.d.ts +4 -2
- package/dist/main/services/workbench-context.js +1 -0
- package/dist/main/services/workspace/op-lock.d.ts +26 -0
- package/dist/main/services/workspace/op-lock.js +23 -0
- package/dist/main/services/workspace/workspace-service.d.ts +7 -0
- package/dist/main/services/workspace/workspace-service.js +197 -103
- package/dist/main/windows/service-host-window/create.d.ts +5 -1
- package/dist/main/windows/service-host-window/create.js +6 -3
- package/dist/native-host/render/render.js +485 -449
- package/dist/native-host/service/service.js +1 -1
- package/dist/renderer/assets/index-BMwmmaSp.js +46 -0
- package/dist/renderer/entries/main/index.html +1 -1
- package/dist/shared/types.d.ts +15 -0
- package/dist/vscode-workbench/assets/__vite-browser-external-B0DTNerG.js +1 -0
- package/dist/vscode-workbench/assets/__vite-browser-external-BuLjVMfN.js +1 -0
- package/dist/vscode-workbench/assets/__vite-browser-external-thkx7I19.js +1 -0
- package/dist/vscode-workbench/assets/{dist-SFZ2HNEz.js → dist-CS7SQP3K.js} +4 -4
- package/dist/vscode-workbench/assets/dist-VSgdjUlW.js +8 -0
- package/dist/vscode-workbench/assets/editor.worker-DmMg51ee.js +41 -0
- package/dist/vscode-workbench/assets/extensionHost.worker-BMpl1cpB.js +207 -0
- package/dist/vscode-workbench/assets/{iconv-lite-umd-izd5l6fa.js → iconv-lite-umd-D3q-1-o6.js} +1 -1
- package/dist/vscode-workbench/assets/{iconv-lite-umd-COfjYshr.js → iconv-lite-umd-Jn9RTrsv.js} +2 -2
- package/dist/vscode-workbench/assets/index-zjigpZ25.js +2842 -0
- package/dist/vscode-workbench/assets/{jschardet-Bf-VMK4F.js → jschardet-CjCVc2Gw.js} +1 -1
- package/dist/vscode-workbench/assets/{jschardet-Dh5TyKsQ.js → jschardet-Cu4ufeHq.js} +1 -1
- package/dist/vscode-workbench/assets/{layout.contribution.darwin-cz2ttHTW.js → layout.contribution.darwin-CoH1BZtz.js} +1 -1
- package/dist/vscode-workbench/assets/{layout.contribution.linux-fEtdzbcY.js → layout.contribution.linux-Di2FF-CG.js} +1 -1
- package/dist/vscode-workbench/assets/{layout.contribution.win-BFyOKI8c.js → layout.contribution.win-moiNStil.js} +1 -1
- package/dist/vscode-workbench/assets/{main-Bh8TI5LP.js → main-DzHL45xo.js} +4 -4
- package/dist/vscode-workbench/assets/{main2-Bds9Fhfn.js → main2-CWVd-Hyp.js} +1 -1
- package/dist/vscode-workbench/assets/{worker-D9ti6A0r.js → worker-CRTb-Arc.js} +7 -7
- package/dist/vscode-workbench/assets/{worker-DQtXDXIZ.js → worker-CVB8WPuM.js} +2 -2
- package/dist/vscode-workbench/index.html +1 -4
- package/package.json +8 -38
- package/dist/renderer/assets/index-6XUSo6Su.js +0 -46
- package/dist/vscode-workbench/assets/__vite-browser-external-BUwiolNy.js +0 -1
- package/dist/vscode-workbench/assets/__vite-browser-external-CmG9GjjO.js +0 -1
- package/dist/vscode-workbench/assets/__vite-browser-external-DGJPTKLg.js +0 -1
- package/dist/vscode-workbench/assets/_commonjsHelpers-qcUHfnrg.js +0 -1
- package/dist/vscode-workbench/assets/chunk-B3K2TuZy.js +0 -1
- package/dist/vscode-workbench/assets/chunk-DECur_0Z.js +0 -1
- package/dist/vscode-workbench/assets/dist-tesJ1Kd2.js +0 -8
- package/dist/vscode-workbench/assets/editor.worker-BC1qOyXG.js +0 -41
- package/dist/vscode-workbench/assets/extension.api-BWA2aPAU.js +0 -1
- package/dist/vscode-workbench/assets/extensionHost.worker-Dvu35anN.js +0 -207
- package/dist/vscode-workbench/assets/index-CQ5xP0pp.js +0 -1656
- package/dist/vscode-workbench/assets/localExtensionHost-Ccz7QUdA.js +0 -1188
- package/dist/vscode-workbench/assets/main-BDgdQmvs.js +0 -4
- package/dist/vscode-workbench/assets/main2-DwfDVleQ.js +0 -1
- package/dist/vscode-workbench/assets/preload-helper-xBbMyY7u.js +0 -1
- /package/dist/vscode-workbench/assets/{_.contribution-C9aS_G-o.js → _.contribution-B46wcGKQ.js} +0 -0
- /package/dist/vscode-workbench/assets/{_commonjsHelpers-BPKu-ps5.js → _commonjsHelpers-DMmVv2OA.js} +0 -0
|
@@ -3,17 +3,23 @@ import * as repo from '../projects/project-repository.js';
|
|
|
3
3
|
import { DEFAULT_COMPILE_CONFIG } from '../projects/types.js';
|
|
4
4
|
import { clearSimulatorServicewechatReferer, setSimulatorServicewechatReferer, } from '../simulator/referer.js';
|
|
5
5
|
import { loadWorkbenchSettings } from '../settings/index.js';
|
|
6
|
+
import { createOpLock } from './op-lock.js';
|
|
6
7
|
/**
|
|
7
|
-
* Runtime guard for the adapter-return boundary: `session.appInfo` must
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* through
|
|
8
|
+
* Runtime guard for the adapter-return boundary: `session.appInfo` must carry a
|
|
9
|
+
* NON-EMPTY string `appId` (the renderer scopes IPC by it; the bridge's
|
|
10
|
+
* handleSpawn also rejects empty appIds, so accepting `''` would desync layers).
|
|
11
|
+
* Loose: extra fields pass through — only the contract-critical `appId` is enforced.
|
|
11
12
|
*/
|
|
12
|
-
const SessionAppInfoSchema = z.looseObject({ appId: z.string() });
|
|
13
|
+
const SessionAppInfoSchema = z.looseObject({ appId: z.string().min(1) });
|
|
13
14
|
/** Build a workspace service bound to the given workbench context. */
|
|
14
15
|
export function createWorkspaceService(ctx) {
|
|
15
16
|
let currentSession = null;
|
|
16
17
|
let currentProjectPath = '';
|
|
18
|
+
// True only while closeProject tears down. disposeSession nulls currentSession
|
|
19
|
+
// before awaiting session.close(), but the bridge app session lives until the
|
|
20
|
+
// later disposeAll — bridge getters consult this to refuse resolving the dying
|
|
21
|
+
// project's guest during that window.
|
|
22
|
+
let closing = false;
|
|
17
23
|
// The last project path cleared by `closeProject`; consumed by project-fs to
|
|
18
24
|
// accept an in-flight write that targets the just-closed project. Reset at
|
|
19
25
|
// the start of every `openProject` so it never accumulates a stale root.
|
|
@@ -28,6 +34,10 @@ export function createWorkspaceService(ctx) {
|
|
|
28
34
|
// first compile's log lines arrive while the adapter promise is still
|
|
29
35
|
// pending, before currentSession is assigned.)
|
|
30
36
|
let logGeneration = 0;
|
|
37
|
+
// Serializes openProject/closeProject teardown/commit sections (FIFO) + latest-
|
|
38
|
+
// wins request tokens, so a close mid-open can't disposeAll() the views the open
|
|
39
|
+
// is building or desync currentSession vs the bridge appSessions. See op-lock.ts.
|
|
40
|
+
const opLock = createOpLock();
|
|
31
41
|
function sendStatus(status, message, hotReload) {
|
|
32
42
|
ctx.notify.projectStatus(hotReload ? { status, message, hotReload: true } : { status, message });
|
|
33
43
|
}
|
|
@@ -54,6 +64,102 @@ export function createWorkspaceService(ctx) {
|
|
|
54
64
|
console.error('[workspace] session close failed:', err);
|
|
55
65
|
}
|
|
56
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* openProject critical section 1 — tear down the outgoing runtime under the op
|
|
69
|
+
* lock (the unbounded compile runs after this releases, so a queued close isn't
|
|
70
|
+
* blocked). Returns true when superseded (caller aborts without touching the
|
|
71
|
+
* newer request's runtime).
|
|
72
|
+
*/
|
|
73
|
+
async function runOpenTeardown(mySeq) {
|
|
74
|
+
const release = await opLock.acquire();
|
|
75
|
+
try {
|
|
76
|
+
if (!opLock.isOwner(mySeq))
|
|
77
|
+
return true;
|
|
78
|
+
clearSimulatorServicewechatReferer();
|
|
79
|
+
// Switching projects tears down the embedded workbench editor (mirrors one
|
|
80
|
+
// project's disk; saves would else hit the wrong project) AND the native
|
|
81
|
+
// simulator (its 'destroyed' hook owns the bridge session). Back-to-list
|
|
82
|
+
// never calls closeProject, so this is the only deterministic teardown.
|
|
83
|
+
if (currentSession !== null) {
|
|
84
|
+
ctx.views.detachWorkbench();
|
|
85
|
+
ctx.views.detachSimulator();
|
|
86
|
+
}
|
|
87
|
+
// Invalidate the outgoing onLog generation before teardown so the dying
|
|
88
|
+
// worker's buffered lines can't pollute the incoming compile timeline.
|
|
89
|
+
logGeneration++;
|
|
90
|
+
await disposeSession();
|
|
91
|
+
currentProjectPath = '';
|
|
92
|
+
lastClosedProjectPath = '';
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
release();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* openProject critical section 2 — commit the new session under the lock, only
|
|
101
|
+
* if still the latest request. Returns false when superseded (caller discards
|
|
102
|
+
* the freshly-opened session instead of clobbering the newer one).
|
|
103
|
+
*/
|
|
104
|
+
async function runOpenCommit(mySeq, session, projectPath) {
|
|
105
|
+
const release = await opLock.acquire();
|
|
106
|
+
try {
|
|
107
|
+
if (!opLock.isOwner(mySeq))
|
|
108
|
+
return false;
|
|
109
|
+
currentSession = session;
|
|
110
|
+
currentProjectPath = projectPath;
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
release();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Drive the compile adapter for one open (OUTSIDE the op lock): returns the
|
|
119
|
+
* session or an error message, never throws. The onLog generation guard drops
|
|
120
|
+
* a superseded session's buffered lines.
|
|
121
|
+
*/
|
|
122
|
+
async function runCompile(projectPath, sessionGeneration) {
|
|
123
|
+
const { compile } = loadWorkbenchSettings();
|
|
124
|
+
try {
|
|
125
|
+
const session = await ctx.adapter.openProject({
|
|
126
|
+
projectPath,
|
|
127
|
+
sourcemap: true,
|
|
128
|
+
fileTypes: ctx.fileTypes,
|
|
129
|
+
watch: compile.watch,
|
|
130
|
+
onRebuild: () => sendStatus('ready', '编译完成,已热更新', true),
|
|
131
|
+
onBuildError: (err) => sendStatus('error', String(err)),
|
|
132
|
+
onLog: (entry) => {
|
|
133
|
+
if (sessionGeneration !== logGeneration)
|
|
134
|
+
return;
|
|
135
|
+
ctx.notify.compileLog({ stream: entry.stream, text: entry.text, at: Date.now() });
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
return { session };
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
clearSimulatorServicewechatReferer();
|
|
142
|
+
return { error: String(err) };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Adapter-return boundary: a session without a string appId can't be driven by
|
|
147
|
+
* the renderer, so it must never become active. Close the live resources the
|
|
148
|
+
* adapter already spun up (best-effort) and return the error message; null when
|
|
149
|
+
* valid.
|
|
150
|
+
*/
|
|
151
|
+
async function rejectInvalidAppId(session) {
|
|
152
|
+
if (SessionAppInfoSchema.safeParse(session.appInfo).success)
|
|
153
|
+
return null;
|
|
154
|
+
try {
|
|
155
|
+
await session.close();
|
|
156
|
+
}
|
|
157
|
+
catch (closeErr) {
|
|
158
|
+
console.warn('[workspace] closing appId-less adapter session failed (non-fatal):', closeErr);
|
|
159
|
+
}
|
|
160
|
+
return 'adapter returned session.appInfo without a string appId — '
|
|
161
|
+
+ 'the CompilationAdapter must supply appInfo.appId';
|
|
162
|
+
}
|
|
57
163
|
function applyRefererFromSession(session) {
|
|
58
164
|
const appInfo = session.appInfo;
|
|
59
165
|
if (appInfo && typeof appInfo.appId === 'string' && appInfo.appId.length > 0) {
|
|
@@ -80,54 +186,29 @@ export function createWorkspaceService(ctx) {
|
|
|
80
186
|
? provider.validateProjectDir(dirPath)
|
|
81
187
|
: null,
|
|
82
188
|
async openProject(projectPath) {
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
//
|
|
87
|
-
//
|
|
189
|
+
// Claim a request seq before the veto hook's await so latest-wins is by
|
|
190
|
+
// REQUEST order, not by which hook resolves first.
|
|
191
|
+
const mySeq = opLock.nextSeq();
|
|
192
|
+
// Host permission gate (WorkbenchAppConfig.onBeforeOpenProject): a throwing
|
|
193
|
+
// hook vetoes the open and leaves the active session intact. Runs before
|
|
194
|
+
// any side effect and outside the op lock/token.
|
|
88
195
|
if (ctx.onBeforeOpenProject) {
|
|
89
196
|
try {
|
|
90
197
|
await ctx.onBeforeOpenProject(projectPath);
|
|
91
198
|
}
|
|
92
199
|
catch (err) {
|
|
93
|
-
// Surface the veto to the status bar — symmetric with the
|
|
94
|
-
// validateProjectDir rejection below (which already emits an error
|
|
95
|
-
// status). The framework caught the error, so it owns the minimal
|
|
96
|
-
// user-visible feedback; a host gate must not have to thread
|
|
97
|
-
// `notify` through a singleton just to report why the open was
|
|
98
|
-
// denied. The host can still layer richer UX (e.g. a dialog).
|
|
99
200
|
const error = err instanceof Error ? err.message : String(err);
|
|
100
201
|
sendStatus('error', error);
|
|
101
202
|
return { success: false, error };
|
|
102
203
|
}
|
|
103
204
|
}
|
|
104
|
-
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
// (wrong project). Detaching destroys the WCV so the next time the
|
|
111
|
-
// 'editor' slot becomes visible it lazily re-attaches and re-mirrors the
|
|
112
|
-
// new project (setWorkbenchBounds re-creates from the stored source on
|
|
113
|
-
// the first non-zero rect). Guarded on an active session so the first
|
|
114
|
-
// open (no predecessor) is a safe no-op.
|
|
115
|
-
if (currentSession !== null) {
|
|
116
|
-
ctx.views.detachWorkbench();
|
|
205
|
+
// Passed the veto — take ownership (a vetoed open never supersedes an
|
|
206
|
+
// in-flight one; ownerSeq only grows, so a late-resolving earlier request
|
|
207
|
+
// can't clobber a later one that already took over).
|
|
208
|
+
opLock.takeOwnership(mySeq);
|
|
209
|
+
if (await runOpenTeardown(mySeq)) {
|
|
210
|
+
return { success: false, error: 'superseded by a newer project open/close' };
|
|
117
211
|
}
|
|
118
|
-
// Invalidate the outgoing session's onLog BEFORE teardown starts (same
|
|
119
|
-
// order as closeProject below): the dying compile worker flushes
|
|
120
|
-
// buffered lines DURING disposeSession(), and with the old generation
|
|
121
|
-
// still current they would pass the staleness guard and pollute the
|
|
122
|
-
// incoming project's compile-log timeline. The new session claims its
|
|
123
|
-
// own fresh generation further down (after dispose), so its onLog
|
|
124
|
-
// forwards normally.
|
|
125
|
-
logGeneration++;
|
|
126
|
-
await disposeSession();
|
|
127
|
-
currentProjectPath = '';
|
|
128
|
-
// A fresh open starts a clean sandbox window — drop any previously
|
|
129
|
-
// recorded last-closed root so it can't accept writes after this point.
|
|
130
|
-
lastClosedProjectPath = '';
|
|
131
212
|
// Reject obviously broken projects up front so we never spin up a
|
|
132
213
|
// compile/server that the simulator will then fetch asset-by-asset.
|
|
133
214
|
// Without this guard, deleted recent-list entries and typo'd CLI paths
|
|
@@ -143,61 +224,32 @@ export function createWorkspaceService(ctx) {
|
|
|
143
224
|
return { success: false, error: dirError };
|
|
144
225
|
}
|
|
145
226
|
sendStatus('compiling', '正在编译...');
|
|
146
|
-
const { compile } = loadWorkbenchSettings();
|
|
147
227
|
// This open owns the log channel until the next open/close bumps the
|
|
148
|
-
// generation —
|
|
228
|
+
// generation — runCompile's onLog closure checks it per line.
|
|
149
229
|
const sessionGeneration = ++logGeneration;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
sourcemap: true,
|
|
155
|
-
watch: compile.watch,
|
|
156
|
-
onRebuild: () => sendStatus('ready', '编译完成,已热更新', true),
|
|
157
|
-
onBuildError: (err) => sendStatus('error', String(err)),
|
|
158
|
-
// Per-line dmcc log (already filtered in devkit). Stamp the
|
|
159
|
-
// wall-clock capture time here and push verbatim on the dedicated
|
|
160
|
-
// compile-log channel — never through projectStatus, whose
|
|
161
|
-
// one-event-per-payload contract feeds compileEvents. Stale lines
|
|
162
|
-
// (a closed/replaced session's worker flushing its buffers) are
|
|
163
|
-
// dropped via the generation check.
|
|
164
|
-
onLog: (entry) => {
|
|
165
|
-
if (sessionGeneration !== logGeneration)
|
|
166
|
-
return;
|
|
167
|
-
ctx.notify.compileLog({
|
|
168
|
-
stream: entry.stream,
|
|
169
|
-
text: entry.text,
|
|
170
|
-
at: Date.now(),
|
|
171
|
-
});
|
|
172
|
-
},
|
|
173
|
-
});
|
|
230
|
+
const compiled = await runCompile(projectPath, sessionGeneration);
|
|
231
|
+
if ('error' in compiled) {
|
|
232
|
+
sendStatus('error', compiled.error);
|
|
233
|
+
return { success: false, error: compiled.error };
|
|
174
234
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
235
|
+
const session = compiled.session;
|
|
236
|
+
const appIdError = await rejectInvalidAppId(session);
|
|
237
|
+
if (appIdError) {
|
|
238
|
+
sendStatus('error', appIdError);
|
|
239
|
+
return { success: false, error: appIdError };
|
|
179
240
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
// live resources (compile watcher, dev-server port) — close them
|
|
185
|
-
// best-effort before reporting; the validation report is the law and a
|
|
186
|
-
// failing close() must not mask it (or escape as a throw).
|
|
187
|
-
if (!SessionAppInfoSchema.safeParse(session.appInfo).success) {
|
|
241
|
+
if (!(await runOpenCommit(mySeq, session, projectPath))) {
|
|
242
|
+
// Superseded during the compile above — discard the freshly-opened
|
|
243
|
+
// session (close its live compile/dev-server) rather than clobber the
|
|
244
|
+
// newer request.
|
|
188
245
|
try {
|
|
189
246
|
await session.close();
|
|
190
247
|
}
|
|
191
248
|
catch (closeErr) {
|
|
192
|
-
console.warn('[workspace] closing
|
|
249
|
+
console.warn('[workspace] closing superseded open session failed (non-fatal):', closeErr);
|
|
193
250
|
}
|
|
194
|
-
|
|
195
|
-
'the CompilationAdapter must supply appInfo.appId';
|
|
196
|
-
sendStatus('error', error);
|
|
197
|
-
return { success: false, error };
|
|
251
|
+
return { success: false, error: 'superseded by a newer project open/close' };
|
|
198
252
|
}
|
|
199
|
-
currentSession = session;
|
|
200
|
-
currentProjectPath = projectPath;
|
|
201
253
|
bestEffort('updateLastOpened', () => {
|
|
202
254
|
if (provider.updateLastOpened)
|
|
203
255
|
provider.updateLastOpened(projectPath);
|
|
@@ -211,37 +263,79 @@ export function createWorkspaceService(ctx) {
|
|
|
211
263
|
};
|
|
212
264
|
},
|
|
213
265
|
async closeProject() {
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
266
|
+
// Claim a request seq and take ownership immediately (close has no veto),
|
|
267
|
+
// then serialize against a concurrent openProject: disposeAll() must not
|
|
268
|
+
// run while an open is mid-teardown/commit (it would tear down the views
|
|
269
|
+
// the open is building).
|
|
270
|
+
const mySeq = opLock.nextSeq();
|
|
271
|
+
opLock.takeOwnership(mySeq);
|
|
272
|
+
const release = await opLock.acquire();
|
|
273
|
+
try {
|
|
274
|
+
// Superseded by a newer open/close that arrived while we waited for the
|
|
275
|
+
// lock: that request now owns the runtime, so disposing here would tear
|
|
276
|
+
// down ITS views. Skip — the newer op's own teardown covers the session
|
|
277
|
+
// we meant to close.
|
|
278
|
+
if (!opLock.isOwner(mySeq))
|
|
279
|
+
return;
|
|
280
|
+
// Mark the close in progress BEFORE disposeSession nulls currentSession,
|
|
281
|
+
// so bridge getters refuse to resolve the dying session during the
|
|
282
|
+
// session.close() await (cleared in the finally once views are gone).
|
|
283
|
+
closing = true;
|
|
284
|
+
// Invalidate the active session's onLog BEFORE teardown starts — lines
|
|
285
|
+
// flushed by the dying compile worker must not reach the panel.
|
|
286
|
+
logGeneration++;
|
|
287
|
+
clearSimulatorServicewechatReferer();
|
|
288
|
+
await disposeSession();
|
|
289
|
+
// Record the root being torn down BEFORE clearing it, so a teardown/
|
|
290
|
+
// debounced write already in flight can still be accepted against it.
|
|
291
|
+
if (currentProjectPath !== '')
|
|
292
|
+
lastClosedProjectPath = currentProjectPath;
|
|
293
|
+
currentProjectPath = '';
|
|
294
|
+
ctx.views.disposeAll();
|
|
295
|
+
}
|
|
296
|
+
finally {
|
|
297
|
+
closing = false;
|
|
298
|
+
release();
|
|
299
|
+
}
|
|
225
300
|
},
|
|
226
301
|
getSession: () => currentSession,
|
|
227
302
|
getProjectPath: () => currentProjectPath,
|
|
228
303
|
getLastClosedProjectPath: () => lastClosedProjectPath,
|
|
229
304
|
hasActiveSession: () => currentSession !== null,
|
|
305
|
+
isClosing: () => closing,
|
|
230
306
|
async captureThumbnail(projectPath) {
|
|
231
307
|
if (!currentSession || projectPath !== currentProjectPath)
|
|
232
308
|
return null;
|
|
233
|
-
const
|
|
234
|
-
if (!
|
|
309
|
+
const simulatorWc = ctx.views.getSimulatorWebContents();
|
|
310
|
+
if (!simulatorWc)
|
|
235
311
|
return null;
|
|
236
312
|
if (ctx.views.getSimulatorProjectPath() !== projectPath)
|
|
237
313
|
return null;
|
|
238
314
|
const session = currentSession;
|
|
315
|
+
// Capture the active render-host guest (the mini-program content); the
|
|
316
|
+
// simulator WCV only holds the device-shell chrome. In native-host with no
|
|
317
|
+
// active guest (not mounted / mid page-switch / destroyed) return null
|
|
318
|
+
// rather than persist a device-shell frame as the page — that wrong-content
|
|
319
|
+
// frame is the bug this path exists to avoid. Non-native-host: the sim WCV.
|
|
320
|
+
const bridge = ctx.bridge;
|
|
321
|
+
const nativeHost = bridge?.isNativeHost() ?? false;
|
|
322
|
+
const renderGuest = nativeHost ? (bridge?.getActiveRenderWc() ?? null) : null;
|
|
323
|
+
if (nativeHost && !renderGuest)
|
|
324
|
+
return null;
|
|
325
|
+
const captureTarget = renderGuest ?? simulatorWc;
|
|
326
|
+
// A destroyed target means teardown is in flight (no valid frame) — return
|
|
327
|
+
// null rather than let capturePage() throw indistinguishably into the catch.
|
|
328
|
+
if (captureTarget.isDestroyed())
|
|
329
|
+
return null;
|
|
239
330
|
try {
|
|
240
|
-
const image = await
|
|
331
|
+
const image = await captureTarget.capturePage();
|
|
241
332
|
if (currentSession !== session
|
|
242
333
|
|| currentProjectPath !== projectPath
|
|
243
|
-
|| ctx.views.getSimulatorWebContents() !==
|
|
244
|
-
|| ctx.views.getSimulatorProjectPath() !== projectPath
|
|
334
|
+
|| ctx.views.getSimulatorWebContents() !== simulatorWc
|
|
335
|
+
|| ctx.views.getSimulatorProjectPath() !== projectPath
|
|
336
|
+
// Captured target must still be the live one: a guest that navigated
|
|
337
|
+
// mid-capture would otherwise persist a frame from the wrong page.
|
|
338
|
+
|| (nativeHost ? (bridge?.getActiveRenderWc() ?? null) : simulatorWc) !== captureTarget) {
|
|
245
339
|
return null;
|
|
246
340
|
}
|
|
247
341
|
const dataUrl = `data:image/png;base64,${image.toPNG().toString('base64')}`;
|
|
@@ -12,6 +12,10 @@ export declare const serviceHostPreloadPath: string;
|
|
|
12
12
|
export interface ServiceHostWindowOptions {
|
|
13
13
|
bridgeId: string;
|
|
14
14
|
appId: string;
|
|
15
|
+
/** Current project root. Folded into the session partition (with appId) so a
|
|
16
|
+
* different project declaring the same appId gets isolated storage. Omitted on
|
|
17
|
+
* the pre-warm pool path (no project known yet → shared partition). */
|
|
18
|
+
projectPath?: string;
|
|
15
19
|
pagePath: string;
|
|
16
20
|
pkgRoot: string;
|
|
17
21
|
root?: string;
|
|
@@ -59,7 +63,7 @@ export declare function navigateServiceHost(win: BrowserWindow, url: string): Pr
|
|
|
59
63
|
* path, which passes the project's appId. Reconciling pooling with per-project
|
|
60
64
|
* partitions (e.g. per-partition sub-pools) is out of scope for this change.
|
|
61
65
|
*/
|
|
62
|
-
export declare function serviceHostSpec(appId?: string): ServiceHostSpec;
|
|
66
|
+
export declare function serviceHostSpec(appId?: string, projectPath?: string): ServiceHostSpec;
|
|
63
67
|
/**
|
|
64
68
|
* Construct + navigate a fresh service-host window (the non-pool path).
|
|
65
69
|
* Behavior-identical to the pre-refactor implementation.
|
|
@@ -110,12 +110,15 @@ export function navigateServiceHost(win, url) {
|
|
|
110
110
|
* path, which passes the project's appId. Reconciling pooling with per-project
|
|
111
111
|
* partitions (e.g. per-partition sub-pools) is out of scope for this change.
|
|
112
112
|
*/
|
|
113
|
-
export function serviceHostSpec(appId) {
|
|
113
|
+
export function serviceHostSpec(appId, projectPath) {
|
|
114
114
|
return {
|
|
115
115
|
// Per-project partition when an appId is known (so this project's service
|
|
116
116
|
// host shares storage ONLY with its own render side); the shared partition
|
|
117
117
|
// for the pre-warm pool's default spec (no appId — see KNOWN BLOCKER below).
|
|
118
|
-
|
|
118
|
+
// projectPath joins the key so same-appId/different-path projects isolate;
|
|
119
|
+
// it must match the simulator WCV's partition (view-manager passes the same
|
|
120
|
+
// (appId, projectPath)).
|
|
121
|
+
partition: appId ? miniappPartition(appId, projectPath) : SERVICE_HOST_PARTITION,
|
|
119
122
|
preloadPath: serviceHostPreloadPath,
|
|
120
123
|
size: { width: 980, height: 720 },
|
|
121
124
|
contextIsolation: false,
|
|
@@ -132,7 +135,7 @@ export function createServiceHostWindow(opts) {
|
|
|
132
135
|
// Default (non-pooled) path: pin the service host to THIS project's partition
|
|
133
136
|
// so its logic-layer localStorage/cookies are shared with the project's render
|
|
134
137
|
// side only, never with other projects.
|
|
135
|
-
const win = constructServiceHostWindow({ appId: opts.appId, partition: miniappPartition(opts.appId) });
|
|
138
|
+
const win = constructServiceHostWindow({ appId: opts.appId, partition: miniappPartition(opts.appId, opts.projectPath) });
|
|
136
139
|
void navigateServiceHost(win, buildServiceHostSpawnUrl(opts));
|
|
137
140
|
return win;
|
|
138
141
|
}
|