@dimina-kit/devtools 0.4.0-dev.20260624040247 → 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/dist/main/index.bundle.js +265 -29
- package/dist/main/services/elements-forward/index.js +37 -3
- package/dist/main/services/render-inspect/index.js +182 -6
- 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/view-manager.js +7 -6
- package/dist/preload/instrumentation/wxml-extract.js +53 -7
- 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/open-in-editor.d.ts +9 -2
- package/dist/shared/open-in-editor.js +140 -17
- package/dist/simulator/assets/device-shell-CPCnCp1L.js +2 -0
- package/dist/simulator/assets/{simulator-CtyXt_4V.js → simulator-gMBWKDeO.js} +3 -3
- package/dist/simulator/simulator.html +1 -1
- package/package.json +6 -6
- package/dist/renderer/assets/index-PtsdWlGu.js +0 -50
- package/dist/simulator/assets/device-shell-TtcSXwhb.js +0 -2
|
@@ -2,11 +2,47 @@ import { readFileSync } from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { devtoolsPackageRoot } from '../../utils/paths.js';
|
|
4
4
|
const DEFAULT_SOURCE_PATH = 'dist/render-host/render-inspect.js';
|
|
5
|
+
/**
|
|
6
|
+
* Chrome DevTools' default Elements-panel highlight palette (content / padding /
|
|
7
|
+
* border / margin tints + the size tooltip). Mirroring it makes the WXML panel's
|
|
8
|
+
* hover box visually identical to the embedded Elements panel's.
|
|
9
|
+
*/
|
|
10
|
+
const HIGHLIGHT_CONFIG = {
|
|
11
|
+
showInfo: true,
|
|
12
|
+
showRulers: false,
|
|
13
|
+
showExtensionLines: false,
|
|
14
|
+
contentColor: { r: 111, g: 168, b: 220, a: 0.66 },
|
|
15
|
+
paddingColor: { r: 147, g: 196, b: 125, a: 0.55 },
|
|
16
|
+
borderColor: { r: 255, g: 229, b: 153, a: 0.66 },
|
|
17
|
+
marginColor: { r: 246, g: 178, b: 107, a: 0.66 },
|
|
18
|
+
};
|
|
19
|
+
/** Object group for the per-hover `Runtime.evaluate` reference, released after the draw. */
|
|
20
|
+
const HOVER_OBJECT_GROUP = 'render-inspect-hover';
|
|
21
|
+
/**
|
|
22
|
+
* Upper bound on awaiting the `DOM.enable → Overlay.enable` handshake before
|
|
23
|
+
* highlighting. A hung `DOM.enable` then degrades to a missed paint instead of a
|
|
24
|
+
* stuck hover.
|
|
25
|
+
*/
|
|
26
|
+
const ENABLE_HANDSHAKE_TIMEOUT_MS = 500;
|
|
27
|
+
/** Resolve when `p` settles or `ms` elapses, whichever comes first (never rejects). */
|
|
28
|
+
function withTimeout(p, ms) {
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
const timer = setTimeout(resolve, ms);
|
|
31
|
+
p.then(() => { clearTimeout(timer); resolve(); }, () => { clearTimeout(timer); resolve(); });
|
|
32
|
+
});
|
|
33
|
+
}
|
|
5
34
|
export function createRenderInspector(options = {}) {
|
|
6
35
|
const loadSource = options.loadSource ??
|
|
7
36
|
(() => readFileSync(path.join(devtoolsPackageRoot, DEFAULT_SOURCE_PATH), 'utf8'));
|
|
8
37
|
let cachedSource = null;
|
|
9
38
|
const injected = new Set();
|
|
39
|
+
// Debugger sessions THIS service attached itself (nobody else owned one). Only
|
|
40
|
+
// these are detached on guest-destroy; sessions safe-area / Elements-forward
|
|
41
|
+
// own are never touched (single-owner per wc).
|
|
42
|
+
const selfAttached = new Set();
|
|
43
|
+
// Per-wc `DOM.enable → Overlay.enable` handshake, started once and reused so a
|
|
44
|
+
// burst of hovers shares one enable. Cleared when the guest is destroyed.
|
|
45
|
+
const enablePromises = new Map();
|
|
10
46
|
function source() {
|
|
11
47
|
if (cachedSource === null)
|
|
12
48
|
cachedSource = loadSource();
|
|
@@ -37,11 +73,15 @@ export function createRenderInspector(options = {}) {
|
|
|
37
73
|
// Consolidate the per-wc bookkeeping teardown onto the connection layer
|
|
38
74
|
// (foundation.md §4 / P2) when a registry is available; fall back to the
|
|
39
75
|
// bespoke `once('destroyed')` only when omitted (focused unit tests).
|
|
76
|
+
const forget = () => {
|
|
77
|
+
injected.delete(wc.id);
|
|
78
|
+
enablePromises.delete(wc.id);
|
|
79
|
+
};
|
|
40
80
|
if (options.connections) {
|
|
41
|
-
options.connections.acquire(wc).own(
|
|
81
|
+
options.connections.acquire(wc).own(forget);
|
|
42
82
|
}
|
|
43
83
|
else {
|
|
44
|
-
wc.once('destroyed',
|
|
84
|
+
wc.once('destroyed', forget);
|
|
45
85
|
}
|
|
46
86
|
return true;
|
|
47
87
|
}
|
|
@@ -63,21 +103,157 @@ export function createRenderInspector(options = {}) {
|
|
|
63
103
|
return null;
|
|
64
104
|
if (!(await ensureInjected(wc)))
|
|
65
105
|
return null;
|
|
106
|
+
let inspection;
|
|
66
107
|
try {
|
|
67
108
|
const result = await wc.executeJavaScript(`window.__diminaRenderInspect ? window.__diminaRenderInspect.highlightElement(${JSON.stringify(sid)}) : null`);
|
|
68
|
-
|
|
109
|
+
inspection = result ?? null;
|
|
69
110
|
}
|
|
70
111
|
catch {
|
|
71
112
|
return null;
|
|
72
113
|
}
|
|
114
|
+
// No geometry → nothing to draw; never touch the debugger.
|
|
115
|
+
if (!inspection)
|
|
116
|
+
return null;
|
|
117
|
+
// Draw the native overlay best-effort: the inspection data must survive even
|
|
118
|
+
// if the CDP draw fails, so a rejected debugger step never propagates.
|
|
119
|
+
await drawNativeHighlight(wc, sid).catch(() => { });
|
|
120
|
+
return inspection;
|
|
73
121
|
}
|
|
74
|
-
|
|
122
|
+
/**
|
|
123
|
+
* Paint the Chrome-style native highlight over the guest via CDP — the same
|
|
124
|
+
* `Overlay.highlightNode` the embedded Elements panel uses, so the WXML panel's
|
|
125
|
+
* hover box matches it exactly. Reuses the guest's existing debugger session
|
|
126
|
+
* (single-owner per wc; safe-area / Elements-forward has usually already
|
|
127
|
+
* attached it) and only attaches itself when nobody has.
|
|
128
|
+
*
|
|
129
|
+
* `Overlay.highlightNode` paints only while the Overlay domain is enabled, and
|
|
130
|
+
* Chromium rejects `Overlay.enable` with "DOM should be enabled first" unless
|
|
131
|
+
* DOM is enabled first. On a cold/self-attached session (WXML hovered without
|
|
132
|
+
* the Elements panel ever enabling Overlay) the highlight would silently no-op
|
|
133
|
+
* if the command raced ahead of the enable, so we AWAIT the `DOM.enable →
|
|
134
|
+
* Overlay.enable` handshake before highlighting — but only up to
|
|
135
|
+
* `ENABLE_HANDSHAKE_TIMEOUT_MS`, so a hung `DOM.enable` degrades to a missed
|
|
136
|
+
* paint rather than a stuck hover.
|
|
137
|
+
*
|
|
138
|
+
* The `Runtime.evaluate` runs in a named object group whose remote references
|
|
139
|
+
* are released in `finally`; otherwise every hover would leak a live DOM
|
|
140
|
+
* wrapper into the guest's execution context.
|
|
141
|
+
*/
|
|
142
|
+
async function drawNativeHighlight(wc, sid) {
|
|
75
143
|
if (wc.isDestroyed())
|
|
76
144
|
return;
|
|
77
|
-
if (!(
|
|
145
|
+
if (!ensureGuestDebugger(wc))
|
|
146
|
+
return;
|
|
147
|
+
await withTimeout(ensureDomainsEnabled(wc), ENABLE_HANDSHAKE_TIMEOUT_MS);
|
|
148
|
+
if (wc.isDestroyed())
|
|
149
|
+
return;
|
|
150
|
+
const expression = `window.__diminaRenderInspect && window.__diminaRenderInspect.elementFor(${JSON.stringify(sid)})`;
|
|
151
|
+
try {
|
|
152
|
+
const evaluated = await wc.debugger.sendCommand('Runtime.evaluate', {
|
|
153
|
+
expression,
|
|
154
|
+
returnByValue: false,
|
|
155
|
+
objectGroup: HOVER_OBJECT_GROUP,
|
|
156
|
+
});
|
|
157
|
+
const objectId = evaluated?.result?.objectId;
|
|
158
|
+
if (!objectId)
|
|
159
|
+
return;
|
|
160
|
+
await wc.debugger.sendCommand('Overlay.highlightNode', {
|
|
161
|
+
objectId,
|
|
162
|
+
highlightConfig: HIGHLIGHT_CONFIG,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
finally {
|
|
166
|
+
// Drop the hover's remote DOM reference so repeated hovers don't accumulate
|
|
167
|
+
// live wrappers in the guest context.
|
|
168
|
+
wc.debugger
|
|
169
|
+
.sendCommand('Runtime.releaseObjectGroup', { objectGroup: HOVER_OBJECT_GROUP })
|
|
170
|
+
.catch(() => { });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Ensure the guest's debugger is usable WITHOUT opening a second session: a
|
|
175
|
+
* webContents debugger is single-owner, so reuse the already-attached session
|
|
176
|
+
* (Elements-forward / safe-area) and only `attach('1.3')` when nobody has. The
|
|
177
|
+
* sessions we open ourselves are tracked so guest-destroy detaches only ours.
|
|
178
|
+
* Returns false when the debugger can't be made usable.
|
|
179
|
+
*/
|
|
180
|
+
function ensureGuestDebugger(wc) {
|
|
181
|
+
try {
|
|
182
|
+
if (wc.debugger.isAttached())
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
wc.debugger.attach('1.3');
|
|
190
|
+
trackSelfAttached(wc);
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
// A concurrent attach (race) leaves it usable; anything else is a failure.
|
|
195
|
+
try {
|
|
196
|
+
return wc.debugger.isAttached();
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/** Record a self-attached session and detach it when the guest is destroyed. */
|
|
204
|
+
function trackSelfAttached(wc) {
|
|
205
|
+
if (selfAttached.has(wc.id))
|
|
206
|
+
return;
|
|
207
|
+
selfAttached.add(wc.id);
|
|
208
|
+
const detach = () => {
|
|
209
|
+
selfAttached.delete(wc.id);
|
|
210
|
+
try {
|
|
211
|
+
if (!wc.isDestroyed() && wc.debugger.isAttached())
|
|
212
|
+
wc.debugger.detach();
|
|
213
|
+
}
|
|
214
|
+
catch { /* already gone */ }
|
|
215
|
+
};
|
|
216
|
+
if (options.connections)
|
|
217
|
+
options.connections.acquire(wc).own(detach);
|
|
218
|
+
else {
|
|
219
|
+
try {
|
|
220
|
+
wc.once('destroyed', () => selfAttached.delete(wc.id));
|
|
221
|
+
}
|
|
222
|
+
catch { /* fake wc */ }
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Enable the DOM + Overlay render domains in dependency order, ONCE per guest
|
|
227
|
+
* (the promise is cached + reused across hovers). `Overlay.enable` is sent ONLY
|
|
228
|
+
* AFTER `DOM.enable` resolves (Chromium rejects it otherwise). Resolves when
|
|
229
|
+
* Overlay is enabled; a rejection at any step is swallowed (the guest may be
|
|
230
|
+
* mid-teardown) and the cache entry is dropped so a later hover can retry.
|
|
231
|
+
*/
|
|
232
|
+
function ensureDomainsEnabled(wc) {
|
|
233
|
+
const cached = enablePromises.get(wc.id);
|
|
234
|
+
if (cached)
|
|
235
|
+
return cached;
|
|
236
|
+
wc.debugger.sendCommand('CSS.enable').catch(() => { });
|
|
237
|
+
const handshake = wc.debugger
|
|
238
|
+
.sendCommand('DOM.enable')
|
|
239
|
+
.then(() => {
|
|
240
|
+
if (wc.isDestroyed())
|
|
241
|
+
return;
|
|
242
|
+
return wc.debugger.sendCommand('Overlay.enable');
|
|
243
|
+
})
|
|
244
|
+
.then(() => undefined)
|
|
245
|
+
.catch(() => {
|
|
246
|
+
// Allow a retry on the next hover (e.g. the guest was mid-destroy).
|
|
247
|
+
enablePromises.delete(wc.id);
|
|
248
|
+
});
|
|
249
|
+
enablePromises.set(wc.id, handshake);
|
|
250
|
+
return handshake;
|
|
251
|
+
}
|
|
252
|
+
async function unhighlight(wc) {
|
|
253
|
+
if (wc.isDestroyed())
|
|
78
254
|
return;
|
|
79
255
|
try {
|
|
80
|
-
await wc.
|
|
256
|
+
await wc.debugger.sendCommand('Overlay.hideHighlight');
|
|
81
257
|
}
|
|
82
258
|
catch {
|
|
83
259
|
// best-effort
|
|
@@ -35,7 +35,10 @@
|
|
|
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
43
|
export declare const DEVTOOLS_KEPT_VIEW_IDS: readonly string[];
|
|
41
44
|
/**
|
|
@@ -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; }
|
|
@@ -462,12 +462,13 @@ export function createViewManager(ctx) {
|
|
|
462
462
|
// map to a project-relative path, and broadcast to the renderer's Monaco.
|
|
463
463
|
const openInEditorWiredWcIds = new Set();
|
|
464
464
|
function injectOpenResourceHandler(serviceWc, devtoolsWc) {
|
|
465
|
-
//
|
|
466
|
-
//
|
|
467
|
-
//
|
|
468
|
-
//
|
|
469
|
-
// `
|
|
470
|
-
//
|
|
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.
|
|
471
472
|
const sourceContext = projectSourceContextFromServiceHostUrl(serviceWc.getURL(), ctx.workspace?.getProjectPath?.());
|
|
472
473
|
if (!sourceContext)
|
|
473
474
|
return;
|
|
@@ -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;
|
|
@@ -40,20 +40,23 @@
|
|
|
40
40
|
"dd-picker-body"
|
|
41
41
|
]);
|
|
42
42
|
var FRAMEWORK_TEMPLATE_RE = /^(taro_tmpl|tmpl_\d+)/;
|
|
43
|
+
function normalizeRegisteredName(regName) {
|
|
44
|
+
const base = regName.startsWith("dd-") ? regName.slice(3) : regName;
|
|
45
|
+
return base.startsWith("tpl-") ? base.slice(4) : base;
|
|
46
|
+
}
|
|
43
47
|
function resolveTemplateNameFromParent(instance) {
|
|
44
48
|
const parent = instance.parent;
|
|
45
|
-
|
|
46
|
-
const parentType = parent.type;
|
|
49
|
+
const parentType = parent?.type;
|
|
47
50
|
const components = parentType?.components;
|
|
48
51
|
if (components) {
|
|
49
52
|
for (const [regName, comp] of Object.entries(components)) {
|
|
50
|
-
if (comp === instance.type) return
|
|
53
|
+
if (comp === instance.type) return normalizeRegisteredName(regName);
|
|
51
54
|
}
|
|
52
55
|
}
|
|
53
56
|
const appComponents = instance.appContext?.components;
|
|
54
57
|
if (!appComponents) return null;
|
|
55
58
|
for (const [regName, comp] of Object.entries(appComponents)) {
|
|
56
|
-
if (comp === instance.type) return
|
|
59
|
+
if (comp === instance.type) return normalizeRegisteredName(regName);
|
|
57
60
|
}
|
|
58
61
|
return null;
|
|
59
62
|
}
|
|
@@ -68,15 +71,25 @@
|
|
|
68
71
|
if (typeof path !== "string" || !path) return null;
|
|
69
72
|
return path.startsWith("/") ? path.slice(1) : path;
|
|
70
73
|
}
|
|
74
|
+
function resolveComponentPath(instance) {
|
|
75
|
+
const provides = instance.provides;
|
|
76
|
+
const path = provides?.path;
|
|
77
|
+
if (typeof path !== "string" || !path) return null;
|
|
78
|
+
const parent = instance.parent;
|
|
79
|
+
const parentProvides = parent?.provides;
|
|
80
|
+
if (path === parentProvides?.path) return null;
|
|
81
|
+
return path.startsWith("/") ? path.slice(1) : path;
|
|
82
|
+
}
|
|
71
83
|
function resolveTagName(instance) {
|
|
72
84
|
const type = instance.type;
|
|
73
85
|
if (!type) return "unknown";
|
|
74
86
|
const pagePath = resolvePagePath(instance);
|
|
75
87
|
if (pagePath) return pagePath;
|
|
88
|
+
const componentPath = resolveComponentPath(instance);
|
|
89
|
+
if (componentPath) return componentPath;
|
|
76
90
|
if (typeof type.__tagName === "string") return type.__tagName;
|
|
77
91
|
const name = type.__name || type.name;
|
|
78
92
|
if (!name) {
|
|
79
|
-
if (type.__scopeId && type.components) return "page";
|
|
80
93
|
if (type.__scopeId) return resolveTemplateNameFromParent(instance) || "template";
|
|
81
94
|
return "unknown";
|
|
82
95
|
}
|
|
@@ -120,6 +133,7 @@
|
|
|
120
133
|
}
|
|
121
134
|
function isTransparentComponent(instance, tagName) {
|
|
122
135
|
if (tagName === "unknown") return true;
|
|
136
|
+
if (FRAMEWORK_TEMPLATE_RE.test(tagName)) return true;
|
|
123
137
|
if (tagName === "template") {
|
|
124
138
|
const props = instance.props;
|
|
125
139
|
const is = props?.is;
|
|
@@ -250,26 +264,14 @@
|
|
|
250
264
|
if (!tree) return null;
|
|
251
265
|
return Array.isArray(tree) ? { tagName: "#fragment", attrs: {}, children: tree } : tree;
|
|
252
266
|
};
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
overlay = document.createElement("div");
|
|
257
|
-
overlay.id = "__simulator-highlight";
|
|
258
|
-
overlay.style.cssText = "position:fixed;pointer-events:none;z-index:999999;border:2px solid #1a73e8;background:rgba(26,115,232,0.12);transition:all 0.1s ease;display:none;border-radius:2px;box-sizing:border-box;";
|
|
259
|
-
document.body.appendChild(overlay);
|
|
260
|
-
return overlay;
|
|
267
|
+
const elementFor = (sid) => {
|
|
268
|
+
if (!sid) return null;
|
|
269
|
+
return findElementBySid(document, sid);
|
|
261
270
|
};
|
|
262
271
|
const highlightElement = (sid) => {
|
|
263
|
-
|
|
264
|
-
const el = findElementBySid(document, sid);
|
|
272
|
+
const el = elementFor(sid);
|
|
265
273
|
if (!el) return null;
|
|
266
274
|
const rect = el.getBoundingClientRect();
|
|
267
|
-
const box = ensureOverlay();
|
|
268
|
-
box.style.left = `${rect.left}px`;
|
|
269
|
-
box.style.top = `${rect.top}px`;
|
|
270
|
-
box.style.width = `${rect.width}px`;
|
|
271
|
-
box.style.height = `${rect.height}px`;
|
|
272
|
-
box.style.display = "block";
|
|
273
275
|
const style = el.ownerDocument.defaultView?.getComputedStyle(el);
|
|
274
276
|
if (!style) return null;
|
|
275
277
|
return {
|
|
@@ -288,8 +290,7 @@
|
|
|
288
290
|
};
|
|
289
291
|
};
|
|
290
292
|
const unhighlightElement = () => {
|
|
291
|
-
if (overlay) overlay.style.display = "none";
|
|
292
293
|
};
|
|
293
|
-
g.__diminaRenderInspect = { getWxml, highlightElement, unhighlightElement };
|
|
294
|
+
g.__diminaRenderInspect = { getWxml, highlightElement, elementFor, unhighlightElement };
|
|
294
295
|
}
|
|
295
296
|
})();
|