@dimina-kit/devtools 0.4.0-dev.20260618090552 → 0.4.0-dev.20260624084417
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 +5 -1
- package/dist/main/app/app.js +14 -2
- package/dist/main/index.bundle.js +596 -112
- package/dist/main/ipc/bridge-router.js +13 -8
- package/dist/main/ipc/projects.js +1 -1
- package/dist/main/ipc/simulator.js +1 -1
- package/dist/main/runtime/miniapp-runtime.d.ts +3 -3
- package/dist/main/services/elements-forward/index.js +42 -8
- package/dist/main/services/network-forward/index.d.ts +5 -6
- package/dist/main/services/network-forward/index.js +9 -10
- package/dist/main/services/notifications/renderer-notifier.d.ts +3 -4
- package/dist/main/services/projects/index.js +1 -1
- package/dist/main/services/render-inspect/index.js +182 -6
- package/dist/main/services/service-console/console-api.d.ts +5 -6
- package/dist/main/services/service-console/console-api.js +5 -6
- package/dist/main/services/service-host-pool/pool.d.ts +2 -3
- package/dist/main/services/simulator-storage/index.d.ts +2 -4
- package/dist/main/services/simulator-storage/index.js +2 -4
- package/dist/main/services/simulator-temp-files/index.js +2 -2
- package/dist/main/services/views/devtools-tabs.d.ts +4 -1
- package/dist/main/services/views/devtools-tabs.js +21 -3
- package/dist/main/services/views/host-toolbar-session-runtime.d.ts +4 -4
- package/dist/main/services/views/host-toolbar-session-runtime.js +4 -4
- package/dist/main/services/views/view-manager.d.ts +17 -1
- package/dist/main/services/views/view-manager.js +74 -47
- package/dist/main/services/workspace/workspace-service.js +11 -0
- package/dist/main/windows/service-host-window/create.d.ts +1 -0
- package/dist/main/windows/service-host-window/create.js +1 -0
- package/dist/preload/instrumentation/wxml-extract.js +53 -7
- package/dist/preload/runtime/host-toolbar-port.d.ts +1 -1
- package/dist/preload/runtime/host-toolbar-port.js +1 -1
- package/dist/preload/runtime/host-toolbar-runtime.d.ts +8 -7
- package/dist/preload/runtime/host-toolbar-runtime.js +8 -7
- package/dist/preload/windows/host-toolbar-runtime.cjs.map +2 -2
- package/dist/preload/windows/main.cjs.map +2 -2
- package/dist/preload/windows/simulator.cjs +1 -1
- package/dist/preload/windows/simulator.cjs.map +1 -1
- package/dist/preload/windows/simulator.js +1 -1
- package/dist/render-host/render-inspect.js +24 -23
- package/dist/renderer/assets/index-BMqriQNr.js +50 -0
- package/dist/renderer/entries/main/index.html +1 -1
- package/dist/shared/appdata-accumulator.d.ts +2 -2
- package/dist/shared/appdata-accumulator.js +3 -3
- package/dist/shared/constants.d.ts +1 -1
- package/dist/shared/constants.js +1 -1
- package/dist/shared/ipc-channels.d.ts +3 -3
- package/dist/shared/ipc-channels.js +3 -3
- package/dist/shared/open-in-editor.d.ts +27 -1
- package/dist/shared/open-in-editor.js +369 -3
- package/dist/shared/vpath.d.ts +2 -2
- package/dist/shared/vpath.js +2 -2
- package/dist/simulator/assets/device-shell-CPCnCp1L.js +2 -0
- package/dist/simulator/assets/{simulator-sf-D0mhw.js → simulator-gMBWKDeO.js} +3 -3
- package/dist/simulator/simulator.html +1 -1
- package/package.json +6 -4
- package/dist/renderer/assets/index-DmgWoK8N.js +0 -50
- package/dist/simulator/assets/device-shell-BWX7Yopg.js +0 -2
|
@@ -35,9 +35,12 @@
|
|
|
35
35
|
*/
|
|
36
36
|
/**
|
|
37
37
|
* Canonical DevTools panel view ids kept in the DEFAULT tab bar (stable front-end
|
|
38
|
-
* ids, unchanged for many Chromium releases): `elements`, `console`, `network
|
|
38
|
+
* ids, unchanged for many Chromium releases): `elements`, `console`, `network`,
|
|
39
|
+
* `sources`. Sources stays so a source-link click that isn't routed to Monaco
|
|
40
|
+
* (build/runtime chunks, framework frames) still has a panel to reveal in instead
|
|
41
|
+
* of silently no-op'ing.
|
|
39
42
|
*/
|
|
40
|
-
export const DEVTOOLS_KEPT_VIEW_IDS = ['elements', 'console', 'network'];
|
|
43
|
+
export const DEVTOOLS_KEPT_VIEW_IDS = ['elements', 'console', 'network', 'sources'];
|
|
41
44
|
/**
|
|
42
45
|
* Build the `executeJavaScript` source injected into the DevTools front-end
|
|
43
46
|
* WebContents. The kept ids + display names are carried as JSON data literals
|
|
@@ -48,13 +51,19 @@ export function buildCustomizeTabsScript(keptIds = DEVTOOLS_KEPT_VIEW_IDS) {
|
|
|
48
51
|
// fallback path); the DevTools UI may be EN or ZH, so include both.
|
|
49
52
|
const NAME_MAP = {
|
|
50
53
|
elements: ['Elements', '元素'], console: ['Console', '控制台'], network: ['Network', '网络'],
|
|
54
|
+
sources: ['Sources', '来源', '源代码'],
|
|
51
55
|
};
|
|
52
56
|
const keepNames = keptIds.flatMap((id) => NAME_MAP[id] ?? [id]);
|
|
53
57
|
const keepIdsJson = JSON.stringify(JSON.stringify([...keptIds]));
|
|
54
58
|
const keepNamesJson = JSON.stringify(JSON.stringify(keepNames));
|
|
59
|
+
const srcNamesJson = JSON.stringify(JSON.stringify(NAME_MAP.sources));
|
|
60
|
+
const netNamesJson = JSON.stringify(JSON.stringify(NAME_MAP.network));
|
|
55
61
|
return `(function(){try{
|
|
56
62
|
var KEEPID = new Set(JSON.parse(${keepIdsJson}));
|
|
57
63
|
var KEEPNAME = new Set(JSON.parse(${keepNamesJson}));
|
|
64
|
+
var SRCNAME = new Set(JSON.parse(${srcNamesJson}));
|
|
65
|
+
var NETNAME = new Set(JSON.parse(${netNamesJson}));
|
|
66
|
+
var WANT_SOURCES_LAST = KEEPID.has('sources');
|
|
58
67
|
// (1) Locale infobar — official host-preference suppression (NOT localStorage).
|
|
59
68
|
try { var IFH=globalThis.InspectorFrontendHost; if(IFH&&typeof IFH.setPreference==='function'){ IFH.setPreference('disable-locale-info-bar','true'); } }catch(_){}
|
|
60
69
|
function deepCollect(sel,cap){ var out=[],seen=0,stack=[document]; while(stack.length&&seen<(cap||40000)){ var root=stack.pop(); try{var m=root.querySelectorAll?root.querySelectorAll(sel):[];for(var i=0;i<m.length;i++)out.push(m[i]);}catch(_){} try{var all=root.querySelectorAll?root.querySelectorAll('*'):[];for(var j=0;j<all.length;j++){seen++;if(all[j].shadowRoot)stack.push(all[j].shadowRoot);}}catch(_){} } return out; }
|
|
@@ -82,9 +91,18 @@ export function buildCustomizeTabsScript(keptIds = DEVTOOLS_KEPT_VIEW_IDS) {
|
|
|
82
91
|
handled=true;
|
|
83
92
|
}catch(_){} }
|
|
84
93
|
// fallback: registrations not enumerable -> at least clear the bar by id
|
|
85
|
-
if(!handled){ var FALL=['timeline','resources','heap-profiler','
|
|
94
|
+
if(!handled){ var FALL=['timeline','resources','heap-profiler','security','lighthouse','chrome-recorder','coverage','linear-memory-inspector','sensors','rendering','animations','autofill-view','medias','issues-pane']; for(var fr=0;fr<FALL.length;fr++){ try{ if(!KEEPID.has(FALL[fr])) maybeRemove(FALL[fr]); }catch(_){} } }
|
|
86
95
|
} else { domFallback(); }
|
|
87
96
|
})();
|
|
97
|
+
// Keep Sources LAST in the bar (after Network). Default DevTools order puts
|
|
98
|
+
// Sources between Console and Network; nudge it to just after Network. Runs in
|
|
99
|
+
// BOTH paths (registry removal leaves the kept tabs in default order). Self-
|
|
100
|
+
// stable: once Sources follows Network the condition no longer holds.
|
|
101
|
+
if(WANT_SOURCES_LAST){ var ro=0,rt=setInterval(function(){ ro++; try{
|
|
102
|
+
var tabs=deepCollect('[role="tab"]'),src=null,net=null;
|
|
103
|
+
for(var i=0;i<tabs.length;i++){ var n=txt(tabs[i]); if(SRCNAME.has(n))src=tabs[i]; else if(NETNAME.has(n))net=tabs[i]; }
|
|
104
|
+
if(src&&net&&src.parentElement&&src.parentElement===net.parentElement&&(src.compareDocumentPosition(net)&Node.DOCUMENT_POSITION_FOLLOWING)){ net.parentElement.insertBefore(src,net.nextSibling); }
|
|
105
|
+
}catch(_){} if(ro>120)clearInterval(rt); },60); }
|
|
88
106
|
// DOM fallback: only if the ESM module couldn't be resolved at all.
|
|
89
107
|
function domFallback(){ var cachedBar=null;
|
|
90
108
|
function findBar(){ if(cachedBar&&cachedBar.isConnected)return cachedBar; cachedBar=null; var tabs=deepCollect('[role="tab"]'); var a=null; for(var i=0;i<tabs.length;i++){if(KEEPNAME.has(txt(tabs[i]))){a=tabs[i];break;}} if(!a)return null; var p=a.parentElement; while(p){try{if(p.querySelectorAll('[role="tab"]').length>1){cachedBar=p;return p;}}catch(_){} p=p.parentElement;} cachedBar=a.parentElement; return cachedBar; }
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
* Ref-counted registration of the host-toolbar framework runtime on
|
|
3
3
|
* `session.defaultSession`.
|
|
4
4
|
*
|
|
5
|
-
* The toolbar's height advertiser
|
|
6
|
-
* `webPreferences.preload
|
|
7
|
-
*
|
|
5
|
+
* The toolbar's height advertiser does not ride the toolbar WCV's
|
|
6
|
+
* `webPreferences.preload`: a host's `setPreloadPath` would replace it and the
|
|
7
|
+
* strip height would collapse to 0. Instead the runtime bundle
|
|
8
8
|
* (`hostToolbarRuntimePreloadPath`) is registered once per process as a
|
|
9
9
|
* session frame preload; its own guard (`--dimina-host-toolbar` marker +
|
|
10
10
|
* `isMainFrame`) keeps it a zero-footprint no-op in every other defaultSession
|
|
11
11
|
* renderer.
|
|
12
12
|
*
|
|
13
|
-
* Ref-counting
|
|
13
|
+
* Ref-counting: multiple ViewManagers can coexist in one
|
|
14
14
|
* process and share the ONE defaultSession. Each manager acquires at most one
|
|
15
15
|
* reference (on first toolbar need) and releases it in `disposeAll`. Only the
|
|
16
16
|
* first acquire registers; only the last release unregisters (with the id
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
* Ref-counted registration of the host-toolbar framework runtime on
|
|
3
3
|
* `session.defaultSession`.
|
|
4
4
|
*
|
|
5
|
-
* The toolbar's height advertiser
|
|
6
|
-
* `webPreferences.preload
|
|
7
|
-
*
|
|
5
|
+
* The toolbar's height advertiser does not ride the toolbar WCV's
|
|
6
|
+
* `webPreferences.preload`: a host's `setPreloadPath` would replace it and the
|
|
7
|
+
* strip height would collapse to 0. Instead the runtime bundle
|
|
8
8
|
* (`hostToolbarRuntimePreloadPath`) is registered once per process as a
|
|
9
9
|
* session frame preload; its own guard (`--dimina-host-toolbar` marker +
|
|
10
10
|
* `isMainFrame`) keeps it a zero-footprint no-op in every other defaultSession
|
|
11
11
|
* renderer.
|
|
12
12
|
*
|
|
13
|
-
* Ref-counting
|
|
13
|
+
* Ref-counting: multiple ViewManagers can coexist in one
|
|
14
14
|
* process and share the ONE defaultSession. Each manager acquires at most one
|
|
15
15
|
* reference (on first toolbar need) and releases it in `disposeAll`. Only the
|
|
16
16
|
* first acquire registers; only the last release unregisters (with the id
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { WebContents } from 'electron';
|
|
2
2
|
import type { NativeDeviceInfo } from '../../../shared/ipc-channels.js';
|
|
3
|
+
import { type OpenInEditorRequest } from '../../../shared/open-in-editor.js';
|
|
3
4
|
import { type WorkbenchContext } from '../workbench-context.js';
|
|
4
5
|
import { type HostToolbarMessageSubscription } from './host-toolbar-port-channel.js';
|
|
5
6
|
/**
|
|
@@ -17,6 +18,8 @@ export interface ViewManagerContext {
|
|
|
17
18
|
* `createWorkbenchContext` always supplies it.
|
|
18
19
|
*/
|
|
19
20
|
connections: WorkbenchContext['connections'];
|
|
21
|
+
/** Active project root used to validate/open console source locations. */
|
|
22
|
+
workspace?: WorkbenchContext['workspace'];
|
|
20
23
|
/**
|
|
21
24
|
* Absolute path to the simulator preload bundle. Only consumed by the
|
|
22
25
|
* native-host simulator WebContentsView (`attachNativeSimulator`); the
|
|
@@ -82,7 +85,7 @@ export interface ViewManager {
|
|
|
82
85
|
* `simWidth` rides the wire for schema compatibility but is unused: all
|
|
83
86
|
* geometry is anchor-published.
|
|
84
87
|
*/
|
|
85
|
-
attachNativeSimulator(simulatorUrl: string, simWidth: number): void
|
|
88
|
+
attachNativeSimulator(simulatorUrl: string, simWidth: number): Promise<void>;
|
|
86
89
|
/**
|
|
87
90
|
* Destroy and null out the simulator view (e.g. on simulator detach).
|
|
88
91
|
* Also destroys the cached settings view and hides the popover —
|
|
@@ -106,6 +109,8 @@ export interface ViewManager {
|
|
|
106
109
|
getSimulatorWebContentsId(): number | null;
|
|
107
110
|
/** Return the live webContents of the currently attached simulator, or null. */
|
|
108
111
|
getSimulatorWebContents(): WebContents | null;
|
|
112
|
+
/** Return the active project path bound to the current native simulator. */
|
|
113
|
+
getSimulatorProjectPath(): string | null;
|
|
109
114
|
/** Return the settings overlay's WebContents (for renderer-notifier). */
|
|
110
115
|
getSettingsWebContents(): WebContents | null;
|
|
111
116
|
/** Return the webContents ID of the settings overlay if alive, else null. */
|
|
@@ -277,6 +282,17 @@ export interface HostToolbarControl {
|
|
|
277
282
|
*/
|
|
278
283
|
setHeightMode(mode: HostToolbarHeightMode): void;
|
|
279
284
|
}
|
|
285
|
+
export interface ProjectEditorTarget {
|
|
286
|
+
path: string;
|
|
287
|
+
line?: number;
|
|
288
|
+
column?: number;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Resolve a DevTools source request against the service-host URL that created
|
|
292
|
+
* the inspected app. Its pkgRoot is authoritative; the workspace is only a
|
|
293
|
+
* stale-session consistency guard.
|
|
294
|
+
*/
|
|
295
|
+
export declare function resolveProjectEditorTarget(serviceHostUrl: string, activeProjectRoot: string | undefined, req: OpenInEditorRequest, isFile?: (absolutePath: string) => boolean): ProjectEditorTarget | null;
|
|
280
296
|
/**
|
|
281
297
|
* Build a ViewManager bound to the given context. The returned object is the
|
|
282
298
|
* only component allowed to instantiate or add/remove overlay WebContentsViews.
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { ipcMain, nativeTheme, shell, WebContentsView, webContents } from 'electron';
|
|
2
|
+
import fs from 'node:fs';
|
|
2
3
|
import path from 'path';
|
|
3
4
|
import { cjsSiblingPreloadPath, mainPreloadPath } from '../../utils/paths.js';
|
|
4
5
|
import { simDeskBg } from '../../utils/theme.js';
|
|
5
6
|
import { applyNavigationHardening, handleWindowOpenExternal, } from '../../windows/navigation-hardening.js';
|
|
6
7
|
import { SimulatorCustomApiBridgeChannel } from '../../../shared/ipc-channels.js';
|
|
7
|
-
import {
|
|
8
|
+
import { buildDevtoolsProjectSourceLinksScript, decodeOpenInEditorUrl, projectSourceContextFromServiceHostUrl, resourceUrlToProjectRelativePath, } from '../../../shared/open-in-editor.js';
|
|
8
9
|
import { createSafeAreaController } from '../safe-area/index.js';
|
|
9
10
|
import { buildCustomizeTabsScript } from './devtools-tabs.js';
|
|
10
11
|
import { installElementsForward } from '../elements-forward/index.js';
|
|
@@ -16,6 +17,35 @@ import { acquireHostToolbarSessionRuntime, releaseHostToolbarSessionRuntime, } f
|
|
|
16
17
|
import { createHostToolbarPortChannel, } from './host-toolbar-port-channel.js';
|
|
17
18
|
import { parseRoute } from '../../../shared/simulator-route.js';
|
|
18
19
|
import { HEADER_H, HOST_TOOLBAR_RUNTIME_MARKER } from '../../../shared/constants.js';
|
|
20
|
+
/**
|
|
21
|
+
* Resolve a DevTools source request against the service-host URL that created
|
|
22
|
+
* the inspected app. Its pkgRoot is authoritative; the workspace is only a
|
|
23
|
+
* stale-session consistency guard.
|
|
24
|
+
*/
|
|
25
|
+
export function resolveProjectEditorTarget(serviceHostUrl, activeProjectRoot, req, isFile = (absolutePath) => fs.statSync(absolutePath).isFile()) {
|
|
26
|
+
const sourceContext = projectSourceContextFromServiceHostUrl(serviceHostUrl, activeProjectRoot);
|
|
27
|
+
if (!sourceContext)
|
|
28
|
+
return null;
|
|
29
|
+
const rel = resourceUrlToProjectRelativePath(req.url, sourceContext);
|
|
30
|
+
if (!rel)
|
|
31
|
+
return null;
|
|
32
|
+
const absolute = path.resolve(sourceContext.projectRoot, ...rel.split('/'));
|
|
33
|
+
const fromRoot = path.relative(path.resolve(sourceContext.projectRoot), absolute);
|
|
34
|
+
if (!fromRoot || fromRoot.startsWith('..') || path.isAbsolute(fromRoot))
|
|
35
|
+
return null;
|
|
36
|
+
try {
|
|
37
|
+
if (!isFile(absolute))
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
path: rel,
|
|
45
|
+
line: typeof req.line === 'number' ? req.line + 1 : undefined,
|
|
46
|
+
column: typeof req.column === 'number' ? req.column + 1 : undefined,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
19
49
|
/**
|
|
20
50
|
* Build a ViewManager bound to the given context. The returned object is the
|
|
21
51
|
* only component allowed to instantiate or add/remove overlay WebContentsViews.
|
|
@@ -37,6 +67,8 @@ export function createViewManager(ctx) {
|
|
|
37
67
|
// while `simulatorView` above hosts its DevTools in the right panel region.
|
|
38
68
|
let nativeSimulatorView = null;
|
|
39
69
|
let nativeSimulatorViewAdded = false;
|
|
70
|
+
let nativeSimulatorProjectPath = null;
|
|
71
|
+
let settleNativeSimulatorReady = null;
|
|
40
72
|
// Model A: the renderer's measured inner-screen rect is the SOLE authority for
|
|
41
73
|
// the native simulator WCV bounds (see docs/simulator-render-architecture.md).
|
|
42
74
|
// Cache the last rect so a report that lands before attachNativeSimulator (the
|
|
@@ -429,43 +461,18 @@ export function createViewManager(ctx) {
|
|
|
429
461
|
// `devtools-open-url` on the inspected (service-host) wc, which we decode,
|
|
430
462
|
// map to a project-relative path, and broadcast to the renderer's Monaco.
|
|
431
463
|
const openInEditorWiredWcIds = new Set();
|
|
432
|
-
function injectOpenResourceHandler(devtoolsWc) {
|
|
433
|
-
//
|
|
434
|
-
//
|
|
435
|
-
//
|
|
436
|
-
//
|
|
437
|
-
// `
|
|
438
|
-
//
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
const timer = setInterval(() => {
|
|
445
|
-
tries++
|
|
446
|
-
try {
|
|
447
|
-
const Host = globalThis.Host
|
|
448
|
-
const host = Host && Host.InspectorFrontendHost
|
|
449
|
-
if (host && typeof host.setOpenResourceHandler === 'function'
|
|
450
|
-
&& typeof host.openInNewTab === 'function') {
|
|
451
|
-
host.setOpenResourceHandler((url, lineNumber, columnNumber) => {
|
|
452
|
-
try {
|
|
453
|
-
const p = new URLSearchParams()
|
|
454
|
-
p.set('u', String(url))
|
|
455
|
-
if (typeof lineNumber === 'number') p.set('l', String(lineNumber))
|
|
456
|
-
if (typeof columnNumber === 'number') p.set('c', String(columnNumber))
|
|
457
|
-
host.openInNewTab(${scheme} + ':?' + p.toString())
|
|
458
|
-
} catch (_) {}
|
|
459
|
-
})
|
|
460
|
-
clearInterval(timer)
|
|
461
|
-
return
|
|
462
|
-
}
|
|
463
|
-
} catch (_) {}
|
|
464
|
-
if (tries > 80) clearInterval(timer)
|
|
465
|
-
}, 50)
|
|
466
|
-
} catch (_) {}
|
|
467
|
-
})()
|
|
468
|
-
`).catch(() => { });
|
|
464
|
+
function injectOpenResourceHandler(serviceWc, devtoolsWc) {
|
|
465
|
+
// Inject the front-end glue that routes a project source-link click to our
|
|
466
|
+
// Monaco editor instead of the DevTools Sources panel: a capture-phase click
|
|
467
|
+
// interceptor re-emits an encoded sentinel via
|
|
468
|
+
// `InspectorFrontendHost.openInNewTab` → Electron `devtools-open-url`. (The
|
|
469
|
+
// legacy `setOpenResourceHandler` hook this used to rely on is gone in
|
|
470
|
+
// current Chromium, so the script keeps it only as a fallback.) Best-effort:
|
|
471
|
+
// the script is fully try/catch-wrapped so a missing API never throws.
|
|
472
|
+
const sourceContext = projectSourceContextFromServiceHostUrl(serviceWc.getURL(), ctx.workspace?.getProjectPath?.());
|
|
473
|
+
if (!sourceContext)
|
|
474
|
+
return;
|
|
475
|
+
devtoolsWc.executeJavaScript(buildDevtoolsProjectSourceLinksScript(sourceContext)).catch(() => { });
|
|
469
476
|
}
|
|
470
477
|
// ── DevTools tab customization: keep only Elements/Console/Network ──────────
|
|
471
478
|
// Inject into the same DevTools front-end host wc that the console/network
|
|
@@ -502,10 +509,10 @@ export function createViewManager(ctx) {
|
|
|
502
509
|
// wc is recreated per simulatorView, so a fresh host needs the handler set.
|
|
503
510
|
if (!devtoolsWc.isDestroyed()) {
|
|
504
511
|
if (devtoolsWc.isLoading()) {
|
|
505
|
-
devtoolsWc.once('dom-ready', () => injectOpenResourceHandler(devtoolsWc));
|
|
512
|
+
devtoolsWc.once('dom-ready', () => injectOpenResourceHandler(serviceWc, devtoolsWc));
|
|
506
513
|
}
|
|
507
514
|
else {
|
|
508
|
-
injectOpenResourceHandler(devtoolsWc);
|
|
515
|
+
injectOpenResourceHandler(serviceWc, devtoolsWc);
|
|
509
516
|
}
|
|
510
517
|
}
|
|
511
518
|
// Attach the `devtools-open-url` decoder to the inspected service wc once
|
|
@@ -518,13 +525,10 @@ export function createViewManager(ctx) {
|
|
|
518
525
|
const req = decodeOpenInEditorUrl(url);
|
|
519
526
|
if (!req)
|
|
520
527
|
return; // not our sentinel — leave it to Electron's default path
|
|
521
|
-
const
|
|
522
|
-
if (!
|
|
528
|
+
const target = resolveProjectEditorTarget(serviceWc.getURL(), ctx.workspace?.getProjectPath?.(), req);
|
|
529
|
+
if (!target)
|
|
523
530
|
return;
|
|
524
|
-
|
|
525
|
-
const line = typeof req.line === 'number' ? req.line + 1 : undefined;
|
|
526
|
-
const column = typeof req.column === 'number' ? req.column + 1 : undefined;
|
|
527
|
-
ctx.notify.editorOpenFile({ path: rel, line, column });
|
|
531
|
+
ctx.notify.editorOpenFile(target);
|
|
528
532
|
};
|
|
529
533
|
serviceWc.on('devtools-open-url', onOpenUrl);
|
|
530
534
|
// Consolidate teardown onto the connection layer (foundation.md §4 / P2),
|
|
@@ -793,8 +797,12 @@ export function createViewManager(ctx) {
|
|
|
793
797
|
function attachNativeSimulator(simulatorUrl, _simWidth) {
|
|
794
798
|
if (!ctx.preloadPath) {
|
|
795
799
|
console.error('[workbench] attachNativeSimulator — preloadPath unset; cannot mount native simulator');
|
|
796
|
-
return;
|
|
800
|
+
return Promise.resolve();
|
|
797
801
|
}
|
|
802
|
+
// Unblock a superseded IPC invocation. Its renderer effect cleanup marks
|
|
803
|
+
// that generation cancelled, so this cannot schedule a stale capture.
|
|
804
|
+
settleNativeSimulatorReady?.();
|
|
805
|
+
settleNativeSimulatorReady = null;
|
|
798
806
|
// Tear down any previous native simulator view (relaunch / re-open).
|
|
799
807
|
if (nativeSimulatorView) {
|
|
800
808
|
detachNativeCustomApiBridge();
|
|
@@ -813,6 +821,10 @@ export function createViewManager(ctx) {
|
|
|
813
821
|
catch { /* ignore */ }
|
|
814
822
|
nativeSimulatorView = null;
|
|
815
823
|
}
|
|
824
|
+
nativeSimulatorProjectPath = null;
|
|
825
|
+
const ready = new Promise((resolve) => {
|
|
826
|
+
settleNativeSimulatorReady = resolve;
|
|
827
|
+
});
|
|
816
828
|
// Derive THIS project's session partition from the simulator URL's appId so
|
|
817
829
|
// its cookies/localStorage/cache are isolated from every other project (P0
|
|
818
830
|
// debt). Same project → same partition (storage survives a relaunch);
|
|
@@ -840,6 +852,7 @@ export function createViewManager(ctx) {
|
|
|
840
852
|
},
|
|
841
853
|
});
|
|
842
854
|
nativeSimulatorView = view;
|
|
855
|
+
nativeSimulatorProjectPath = ctx.workspace?.getProjectPath() || null;
|
|
843
856
|
// Paint the WCV surface the themed desk color (simDeskBg(): dark #121212 /
|
|
844
857
|
// light #e8e8e8) so a height-resize that grows the region never flashes a
|
|
845
858
|
// mismatched strip before DeviceShell's desk repaints — the WCV, the desk,
|
|
@@ -929,6 +942,15 @@ export function createViewManager(ctx) {
|
|
|
929
942
|
e.preventDefault();
|
|
930
943
|
}
|
|
931
944
|
});
|
|
945
|
+
// The outer DeviceShell loading is insufficient: slow projects attach a
|
|
946
|
+
// render guest later. Resolve the renderer's attach IPC only after that
|
|
947
|
+
// first mini-app page has completed its own document load.
|
|
948
|
+
guestWc.once('did-finish-load', () => {
|
|
949
|
+
if (nativeSimulatorView !== view || simWc.isDestroyed())
|
|
950
|
+
return;
|
|
951
|
+
settleNativeSimulatorReady?.();
|
|
952
|
+
settleNativeSimulatorReady = null;
|
|
953
|
+
});
|
|
932
954
|
// A render-host guest only attaches after a spawn, so the service window
|
|
933
955
|
// exists by now — (re)point the right-panel DevTools at it. Belt-and-braces
|
|
934
956
|
// with the `onRenderEvent` path in case its emit lost the attach race.
|
|
@@ -984,8 +1006,11 @@ export function createViewManager(ctx) {
|
|
|
984
1006
|
// (The UI/view layer's Elements equivalent is the native WXML panel +
|
|
985
1007
|
// render-guest highlight chain, which targets the active render guest.)
|
|
986
1008
|
attachNativeSimulatorDevtoolsHost();
|
|
1009
|
+
return ready;
|
|
987
1010
|
}
|
|
988
1011
|
function detachSimulator() {
|
|
1012
|
+
settleNativeSimulatorReady?.();
|
|
1013
|
+
settleNativeSimulatorReady = null;
|
|
989
1014
|
stopFollowingNativeServiceHost();
|
|
990
1015
|
detachNativeCustomApiBridge();
|
|
991
1016
|
// Stop Elements forwarding (detaches only the debugger sessions IT attached;
|
|
@@ -1029,6 +1054,7 @@ export function createViewManager(ctx) {
|
|
|
1029
1054
|
simulatorView = null;
|
|
1030
1055
|
simulatorViewAdded = false;
|
|
1031
1056
|
simulatorWebContentsId = null;
|
|
1057
|
+
nativeSimulatorProjectPath = null;
|
|
1032
1058
|
// Drop the renderer-published rect so a stale "hidden" override doesn't
|
|
1033
1059
|
// suppress the next view before its renderer republishes.
|
|
1034
1060
|
simulatorBoundsOverride = null;
|
|
@@ -1239,6 +1265,7 @@ export function createViewManager(ctx) {
|
|
|
1239
1265
|
const wc = webContents.fromId(simulatorWebContentsId);
|
|
1240
1266
|
return wc && !wc.isDestroyed() ? wc : null;
|
|
1241
1267
|
},
|
|
1268
|
+
getSimulatorProjectPath: () => nativeSimulatorProjectPath,
|
|
1242
1269
|
getSettingsWebContents: () => {
|
|
1243
1270
|
if (!settingsView)
|
|
1244
1271
|
return null;
|
|
@@ -215,11 +215,22 @@ export function createWorkspaceService(ctx) {
|
|
|
215
215
|
getLastClosedProjectPath: () => lastClosedProjectPath,
|
|
216
216
|
hasActiveSession: () => currentSession !== null,
|
|
217
217
|
async captureThumbnail(projectPath) {
|
|
218
|
+
if (!currentSession || projectPath !== currentProjectPath)
|
|
219
|
+
return null;
|
|
218
220
|
const wc = ctx.views.getSimulatorWebContents();
|
|
219
221
|
if (!wc)
|
|
220
222
|
return null;
|
|
223
|
+
if (ctx.views.getSimulatorProjectPath() !== projectPath)
|
|
224
|
+
return null;
|
|
225
|
+
const session = currentSession;
|
|
221
226
|
try {
|
|
222
227
|
const image = await wc.capturePage();
|
|
228
|
+
if (currentSession !== session
|
|
229
|
+
|| currentProjectPath !== projectPath
|
|
230
|
+
|| ctx.views.getSimulatorWebContents() !== wc
|
|
231
|
+
|| ctx.views.getSimulatorProjectPath() !== projectPath) {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
223
234
|
const dataUrl = `data:image/png;base64,${image.toPNG().toString('base64')}`;
|
|
224
235
|
if (provider.saveThumbnail) {
|
|
225
236
|
await provider.saveThumbnail(projectPath, dataUrl);
|
|
@@ -52,6 +52,7 @@ export function buildServiceHostSpawnUrl(opts) {
|
|
|
52
52
|
url.searchParams.set('appId', opts.appId);
|
|
53
53
|
url.searchParams.set('pagePath', opts.pagePath);
|
|
54
54
|
url.searchParams.set('pkgRoot', opts.pkgRoot);
|
|
55
|
+
url.searchParams.set('root', opts.root || 'main');
|
|
55
56
|
url.searchParams.set('resourceBaseUrl', opts.resourceBaseUrl);
|
|
56
57
|
if (opts.hostEnvSnapshot) {
|
|
57
58
|
// Encode the resolved host-env so preload can hydrate
|
|
@@ -4,16 +4,24 @@ const INTERNAL_CLASSES = new Set([
|
|
|
4
4
|
'dd-picker-overlay', 'dd-picker-container', 'dd-picker-header', 'dd-picker-body',
|
|
5
5
|
]);
|
|
6
6
|
const FRAMEWORK_TEMPLATE_RE = /^(taro_tmpl|tmpl_\d+)/;
|
|
7
|
+
/**
|
|
8
|
+
* Strip the `dd-` registration prefix and the `tpl-` prefix dimina adds to
|
|
9
|
+
* compiled template components (`app.component('dd-tpl-taro_tmpl', …)`), so a
|
|
10
|
+
* framework template wrapper normalizes to its bare name (`taro_tmpl`,
|
|
11
|
+
* `tmpl_0_3`) and `FRAMEWORK_TEMPLATE_RE` recognizes it.
|
|
12
|
+
*/
|
|
13
|
+
function normalizeRegisteredName(regName) {
|
|
14
|
+
const base = regName.startsWith('dd-') ? regName.slice(3) : regName;
|
|
15
|
+
return base.startsWith('tpl-') ? base.slice(4) : base;
|
|
16
|
+
}
|
|
7
17
|
function resolveTemplateNameFromParent(instance) {
|
|
8
18
|
const parent = instance.parent;
|
|
9
|
-
|
|
10
|
-
return null;
|
|
11
|
-
const parentType = parent.type;
|
|
19
|
+
const parentType = parent?.type;
|
|
12
20
|
const components = parentType?.components;
|
|
13
21
|
if (components) {
|
|
14
22
|
for (const [regName, comp] of Object.entries(components)) {
|
|
15
23
|
if (comp === instance.type)
|
|
16
|
-
return
|
|
24
|
+
return normalizeRegisteredName(regName);
|
|
17
25
|
}
|
|
18
26
|
}
|
|
19
27
|
const appComponents = instance.appContext?.components;
|
|
@@ -21,7 +29,7 @@ function resolveTemplateNameFromParent(instance) {
|
|
|
21
29
|
return null;
|
|
22
30
|
for (const [regName, comp] of Object.entries(appComponents)) {
|
|
23
31
|
if (comp === instance.type)
|
|
24
|
-
return
|
|
32
|
+
return normalizeRegisteredName(regName);
|
|
25
33
|
}
|
|
26
34
|
return null;
|
|
27
35
|
}
|
|
@@ -53,6 +61,29 @@ function resolvePagePath(instance) {
|
|
|
53
61
|
return null;
|
|
54
62
|
return path.startsWith('/') ? path.slice(1) : path;
|
|
55
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* A NATIVE dimina custom component (a `usingComponents` entry) calls
|
|
66
|
+
* `provide('path', componentPath)` in its setup (render runtime), so its
|
|
67
|
+
* provided `path` DIFFERS from its parent's. A plain descendant inherits the
|
|
68
|
+
* same provides object (identical `path`) and is not a boundary; a Taro template
|
|
69
|
+
* wrapper (`dd-tpl-*`) never provides, so it never differs either. Comparing
|
|
70
|
+
* against the parent's provided path therefore isolates exactly the native
|
|
71
|
+
* component roots — matching WeChat, which shows each custom component as a node
|
|
72
|
+
* tagged with its full registered path (with a synthetic `#shadow-root` added by
|
|
73
|
+
* `wrapInShadowRoot` since the path contains `/`). Pages are handled earlier via
|
|
74
|
+
* their authoritative `__page__` marker, so this only fires for components.
|
|
75
|
+
*/
|
|
76
|
+
function resolveComponentPath(instance) {
|
|
77
|
+
const provides = instance.provides;
|
|
78
|
+
const path = provides?.path;
|
|
79
|
+
if (typeof path !== 'string' || !path)
|
|
80
|
+
return null;
|
|
81
|
+
const parent = instance.parent;
|
|
82
|
+
const parentProvides = parent?.provides;
|
|
83
|
+
if (path === parentProvides?.path)
|
|
84
|
+
return null;
|
|
85
|
+
return path.startsWith('/') ? path.slice(1) : path;
|
|
86
|
+
}
|
|
56
87
|
function resolveTagName(instance) {
|
|
57
88
|
const type = instance.type;
|
|
58
89
|
if (!type)
|
|
@@ -63,12 +94,22 @@ function resolveTagName(instance) {
|
|
|
63
94
|
const pagePath = resolvePagePath(instance);
|
|
64
95
|
if (pagePath)
|
|
65
96
|
return pagePath;
|
|
97
|
+
// Native custom-component boundary → tag with its full registered path (WeChat
|
|
98
|
+
// parity). Must precede the __tagName/__name/dd- shortening below, which would
|
|
99
|
+
// otherwise collapse `dd-foo` to the bare `foo` and lose the path.
|
|
100
|
+
const componentPath = resolveComponentPath(instance);
|
|
101
|
+
if (componentPath)
|
|
102
|
+
return componentPath;
|
|
66
103
|
if (typeof type.__tagName === 'string')
|
|
67
104
|
return type.__tagName;
|
|
68
105
|
const name = (type.__name || type.name);
|
|
69
106
|
if (!name) {
|
|
70
|
-
|
|
71
|
-
|
|
107
|
+
// A nameless component carrying a `__scopeId` is a compiled page, custom
|
|
108
|
+
// component, or framework template wrapper. The page is already resolved
|
|
109
|
+
// above via its authoritative `__page__` marker, so by here it is NOT a
|
|
110
|
+
// page — recover the registered tag (e.g. a Taro `taro_tmpl`/`tmpl_0_3`
|
|
111
|
+
// wrapper) and fall back to `template`. Defaulting to `page` here would
|
|
112
|
+
// mislabel every such wrapper as a second page root.
|
|
72
113
|
if (type.__scopeId)
|
|
73
114
|
return resolveTemplateNameFromParent(instance) || 'template';
|
|
74
115
|
return 'unknown';
|
|
@@ -132,6 +173,11 @@ function getElementSid(instance) {
|
|
|
132
173
|
function isTransparentComponent(instance, tagName) {
|
|
133
174
|
if (tagName === 'unknown')
|
|
134
175
|
return true;
|
|
176
|
+
// A framework template wrapper (Taro `taro_tmpl` / `tmpl_0_3`, whether named
|
|
177
|
+
// via `props.is` or recovered from its registration) is compiler scaffolding,
|
|
178
|
+
// not user content — pass its children straight through.
|
|
179
|
+
if (FRAMEWORK_TEMPLATE_RE.test(tagName))
|
|
180
|
+
return true;
|
|
135
181
|
if (tagName === 'template') {
|
|
136
182
|
const props = instance.props;
|
|
137
183
|
const is = props?.is;
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* envelope stays the single waist (same posture as the main side's
|
|
20
20
|
* host-toolbar-port-channel.ts).
|
|
21
21
|
*
|
|
22
|
-
* Ordering reality this module absorbs
|
|
22
|
+
* Ordering reality this module absorbs:
|
|
23
23
|
* - The page script runs BEFORE the handshake can complete (the port is
|
|
24
24
|
* posted on did-finish-load). Page `send()`s issued before the port
|
|
25
25
|
* arrives go into a PENDING QUEUE and flush in order on handshake —
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* envelope stays the single waist (same posture as the main side's
|
|
20
20
|
* host-toolbar-port-channel.ts).
|
|
21
21
|
*
|
|
22
|
-
* Ordering reality this module absorbs
|
|
22
|
+
* Ordering reality this module absorbs:
|
|
23
23
|
* - The page script runs BEFORE the handshake can complete (the port is
|
|
24
24
|
* posted on did-finish-load). Page `send()`s issued before the port
|
|
25
25
|
* arrives go into a PENDING QUEUE and flush in order on handshake —
|
|
@@ -10,15 +10,16 @@
|
|
|
10
10
|
* - the toolbar WCV's creation injects `HOST_TOOLBAR_RUNTIME_MARKER` into its
|
|
11
11
|
* process argv via `webPreferences.additionalArguments`;
|
|
12
12
|
* - the marker is PROCESS-level (subframes of the toolbar window carry it
|
|
13
|
-
* too
|
|
14
|
-
*
|
|
13
|
+
* too), so the guard needs BOTH wings: `isMainFrame` AND
|
|
14
|
+
* `argv.includes(marker)`;
|
|
15
15
|
* - a renderer that fails the guard returns immediately with ZERO footprint
|
|
16
|
-
* (no advertiser, no listeners, no globals
|
|
16
|
+
* (no advertiser, no listeners, no globals).
|
|
17
17
|
*
|
|
18
|
-
* Why session-resident at all:
|
|
19
|
-
* `webPreferences.preload
|
|
20
|
-
* preload>)`
|
|
21
|
-
* on the session layer, the host preload and the framework runtime
|
|
18
|
+
* Why session-resident at all: riding the toolbar WCV's
|
|
19
|
+
* `webPreferences.preload` lets a host calling `setPreloadPath(<its own
|
|
20
|
+
* preload>)` REPLACE the advertiser, collapsing the strip height to 0. With the
|
|
21
|
+
* runtime on the session layer, the host preload and the framework runtime
|
|
22
|
+
* coexist.
|
|
22
23
|
*/
|
|
23
24
|
/**
|
|
24
25
|
* Pure guard predicate: should the toolbar runtime activate in a renderer with
|
|
@@ -10,15 +10,16 @@
|
|
|
10
10
|
* - the toolbar WCV's creation injects `HOST_TOOLBAR_RUNTIME_MARKER` into its
|
|
11
11
|
* process argv via `webPreferences.additionalArguments`;
|
|
12
12
|
* - the marker is PROCESS-level (subframes of the toolbar window carry it
|
|
13
|
-
* too
|
|
14
|
-
*
|
|
13
|
+
* too), so the guard needs BOTH wings: `isMainFrame` AND
|
|
14
|
+
* `argv.includes(marker)`;
|
|
15
15
|
* - a renderer that fails the guard returns immediately with ZERO footprint
|
|
16
|
-
* (no advertiser, no listeners, no globals
|
|
16
|
+
* (no advertiser, no listeners, no globals).
|
|
17
17
|
*
|
|
18
|
-
* Why session-resident at all:
|
|
19
|
-
* `webPreferences.preload
|
|
20
|
-
* preload>)`
|
|
21
|
-
* on the session layer, the host preload and the framework runtime
|
|
18
|
+
* Why session-resident at all: riding the toolbar WCV's
|
|
19
|
+
* `webPreferences.preload` lets a host calling `setPreloadPath(<its own
|
|
20
|
+
* preload>)` REPLACE the advertiser, collapsing the strip height to 0. With the
|
|
21
|
+
* runtime on the session layer, the host preload and the framework runtime
|
|
22
|
+
* coexist.
|
|
22
23
|
*/
|
|
23
24
|
import { HOST_TOOLBAR_RUNTIME_MARKER } from '../../shared/constants.js';
|
|
24
25
|
import { installHostToolbarAdvertiserWhenReady } from './host-toolbar-advertiser.js';
|