@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.
@@ -1138,10 +1138,15 @@ function projectAwareResourcePath(resourceUrl, context) {
1138
1138
  const projectRoot = normalizeSlashPath(context.projectRoot).replace(/\/$/, "");
1139
1139
  const raw = resourceUrl.trim();
1140
1140
  if (!raw) return null;
1141
+ const excludeBuildChunk = (rel) => {
1142
+ if (!rel) return rel;
1143
+ const base2 = rel.split("/").pop();
1144
+ return base2 === "common.js" || base2 === "vendors.js" || base2 === "runtime.js" || base2 === "taro.js" || base2 === "babelHelpers.js" ? null : rel;
1145
+ };
1141
1146
  const normalizedRaw = normalizeSlashPath(raw);
1142
1147
  if (projectRoot && (normalizedRaw === projectRoot || normalizedRaw.startsWith(`${projectRoot}/`))) {
1143
1148
  const relative = normalizedRaw.slice(projectRoot.length).replace(/^\/+/, "");
1144
- return safeRelativePath(relative.split("/").filter(Boolean));
1149
+ return excludeBuildChunk(safeRelativePath(relative.split("/").filter(Boolean)));
1145
1150
  }
1146
1151
  if (/^\/(?:Volumes|Users|home|private|tmp|var|opt|Applications)\//.test(normalizedRaw)) {
1147
1152
  return null;
@@ -1156,9 +1161,9 @@ function projectAwareResourcePath(resourceUrl, context) {
1156
1161
  if (resource.protocol === "file:") {
1157
1162
  const filePath = normalizeSlashPath(resource.pathname);
1158
1163
  if (!projectRoot || filePath !== projectRoot && !filePath.startsWith(`${projectRoot}/`)) return null;
1159
- return safeRelativePath(
1164
+ return excludeBuildChunk(safeRelativePath(
1160
1165
  filePath.slice(projectRoot.length).replace(/^\/+/, "").split("/").filter(Boolean)
1161
- );
1166
+ ));
1162
1167
  }
1163
1168
  if (resource.protocol !== "http:" && resource.protocol !== "https:") return null;
1164
1169
  if (resource.origin !== base.origin) return null;
@@ -1167,7 +1172,7 @@ function projectAwareResourcePath(resourceUrl, context) {
1167
1172
  segments = segments.slice(1);
1168
1173
  if (context.outputRoot && segments[0] === context.outputRoot) segments = segments.slice(1);
1169
1174
  }
1170
- return safeRelativePath(segments);
1175
+ return excludeBuildChunk(safeRelativePath(segments));
1171
1176
  }
1172
1177
  function projectSourceContextFromServiceHostUrl(serviceHostUrl, activeProjectRoot) {
1173
1178
  let url;
@@ -1212,6 +1217,7 @@ function buildDevtoolsProjectSourceLinksScript(context) {
1212
1217
  const safeRelativePath = ${safeRelativePath.toString()}
1213
1218
  const observedRoots = new Set()
1214
1219
  const observers = []
1220
+ const clickBindings = []
1215
1221
  let timer = null
1216
1222
  const state = {
1217
1223
  dispose() {
@@ -1222,11 +1228,106 @@ function buildDevtoolsProjectSourceLinksScript(context) {
1222
1228
  for (const observer of observers.splice(0)) {
1223
1229
  try { observer.disconnect() } catch (_) {}
1224
1230
  }
1231
+ for (const binding of clickBindings.splice(0)) {
1232
+ try { binding.target.removeEventListener('click', binding.fn, true) } catch (_) {}
1233
+ }
1225
1234
  observedRoots.clear()
1226
1235
  },
1227
1236
  }
1228
1237
  globalThis[stateKey] = state
1229
1238
 
1239
+ // The InspectorFrontendHost that owns openInNewTab. Across DevTools
1240
+ // versions it lives on the top-level \`InspectorFrontendHost\` global, the
1241
+ // legacy \`Host.InspectorFrontendHost\`, or \`DevToolsHost\`; resolve at call
1242
+ // time so a late-bootstrapped host is still found.
1243
+ function resolveOpenInNewTabHost() {
1244
+ const candidates = [
1245
+ globalThis.InspectorFrontendHost,
1246
+ globalThis.Host && globalThis.Host.InspectorFrontendHost,
1247
+ globalThis.DevToolsHost,
1248
+ ]
1249
+ for (const host of candidates) {
1250
+ if (host && typeof host.openInNewTab === 'function') return host
1251
+ }
1252
+ return null
1253
+ }
1254
+
1255
+ // Encode (url, 0-based line, 0-based column) as the sentinel the main
1256
+ // process decodes from \`devtools-open-url\` and routes to Monaco.
1257
+ function openInEditor(url, line, column) {
1258
+ const host = resolveOpenInNewTabHost()
1259
+ if (!host) return false
1260
+ const p = new URLSearchParams()
1261
+ p.set('u', String(url))
1262
+ if (typeof line === 'number' && isFinite(line)) p.set('l', String(line))
1263
+ if (typeof column === 'number' && isFinite(column)) p.set('c', String(column))
1264
+ try { host.openInNewTab(${scheme} + ':?' + p.toString()) } catch (_) { return false }
1265
+ return true
1266
+ }
1267
+
1268
+ // Resolve a clicked link element to a project source location. Returns
1269
+ // the FULL resource url (the main process re-maps it against the active
1270
+ // project) plus its display line/column when present.
1271
+ function projectLocationForLink(link) {
1272
+ if (!link) return null
1273
+ const candidates = [
1274
+ link.getAttribute && link.getAttribute('data-url'),
1275
+ link.getAttribute && link.getAttribute('href'),
1276
+ link.getAttribute && link.getAttribute('title'),
1277
+ ]
1278
+ for (const candidate of candidates) {
1279
+ if (!candidate) continue
1280
+ const location = splitLocation(candidate)
1281
+ if (!diminaProjectSourcePath(location.url, cfg)) continue
1282
+ const suffix = location.suffix
1283
+ || ((String(link.textContent || '').match(/(:\\d+(?::\\d+)?)$/) || [])[1] || '')
1284
+ return { url: location.url, suffix }
1285
+ }
1286
+ return null
1287
+ }
1288
+
1289
+ // DevTools shows 1-based line:column in link text; the main process
1290
+ // contract expects 0-based (it re-adds 1). Convert here.
1291
+ function parseSuffix(suffix) {
1292
+ const m = String(suffix || '').match(/^:(\\d+)(?::(\\d+))?$/)
1293
+ if (!m) return { line: undefined, column: undefined }
1294
+ const line = Math.max(0, parseInt(m[1], 10) - 1)
1295
+ const column = m[2] != null ? Math.max(0, parseInt(m[2], 10) - 1) : undefined
1296
+ return { line, column }
1297
+ }
1298
+
1299
+ // Capture-phase click handler: route a project-source link click to
1300
+ // Monaco instead of the DevTools Sources panel. Plain primary clicks only
1301
+ // \u2014 modified clicks (cmd/ctrl/shift/alt, middle button) fall through to
1302
+ // DevTools so its own "open in new tab" / multi-select still work.
1303
+ function onLinkClick(event) {
1304
+ if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return
1305
+ const path = typeof event.composedPath === 'function' ? event.composedPath() : []
1306
+ let link = null
1307
+ for (const node of path) {
1308
+ if (node && node.nodeType === 1 && node.classList && node.classList.contains('devtools-link')) {
1309
+ link = node
1310
+ break
1311
+ }
1312
+ }
1313
+ if (!link) return
1314
+ const location = projectLocationForLink(link)
1315
+ if (!location) return
1316
+ const { line, column } = parseSuffix(location.suffix)
1317
+ if (!openInEditor(location.url, line, column)) return
1318
+ event.preventDefault()
1319
+ event.stopImmediatePropagation()
1320
+ }
1321
+
1322
+ function bindClicks(target) {
1323
+ if (!target || !target.addEventListener) return
1324
+ for (const binding of clickBindings) {
1325
+ if (binding.target === target) return
1326
+ }
1327
+ target.addEventListener('click', onLinkClick, true)
1328
+ clickBindings.push({ target, fn: onLinkClick })
1329
+ }
1330
+
1230
1331
  function splitLocation(value) {
1231
1332
  const raw = String(value || '')
1232
1333
  const match = raw.match(/^(.*?)(?::(\\d+))(?::(\\d+))?$/)
@@ -1257,6 +1358,7 @@ function buildDevtoolsProjectSourceLinksScript(context) {
1257
1358
  function observeRoot(root) {
1258
1359
  if (!root || !root.querySelectorAll || observedRoots.has(root)) return
1259
1360
  observedRoots.add(root)
1361
+ bindClicks(root)
1260
1362
 
1261
1363
  const observer = new MutationObserver((records) => {
1262
1364
  for (const record of records) {
@@ -1291,23 +1393,25 @@ function buildDevtoolsProjectSourceLinksScript(context) {
1291
1393
 
1292
1394
  observeRoot(document)
1293
1395
 
1396
+ // Fallback for older DevTools fronts that still expose the
1397
+ // setOpenResourceHandler hook. On current Chromium the method is gone,
1398
+ // so this poll simply times out and the capture-phase click interceptor
1399
+ // is the only live path. When the hook IS present the interceptor still
1400
+ // wins (it runs in capture and stops propagation), so the two never
1401
+ // double-open the same click.
1294
1402
  let tries = 0
1295
1403
  timer = setInterval(() => {
1296
1404
  tries++
1297
1405
  try {
1298
- const Host = globalThis.Host
1299
- const host = Host && Host.InspectorFrontendHost
1300
- if (host && typeof host.setOpenResourceHandler === 'function'
1301
- && typeof host.openInNewTab === 'function') {
1406
+ const host = resolveOpenInNewTabHost()
1407
+ if (host && typeof host.setOpenResourceHandler === 'function') {
1302
1408
  host.setOpenResourceHandler((url, lineNumber, columnNumber) => {
1303
- try {
1304
- if (!diminaProjectSourcePath(String(url), cfg)) return
1305
- const p = new URLSearchParams()
1306
- p.set('u', String(url))
1307
- if (typeof lineNumber === 'number') p.set('l', String(lineNumber))
1308
- if (typeof columnNumber === 'number') p.set('c', String(columnNumber))
1309
- host.openInNewTab(${scheme} + ':?' + p.toString())
1310
- } catch (_) {}
1409
+ if (!diminaProjectSourcePath(String(url), cfg)) return
1410
+ openInEditor(
1411
+ String(url),
1412
+ typeof lineNumber === 'number' ? lineNumber : undefined,
1413
+ typeof columnNumber === 'number' ? columnNumber : undefined,
1414
+ )
1311
1415
  })
1312
1416
  clearInterval(timer)
1313
1417
  timer = null
@@ -1373,19 +1477,25 @@ function createSafeAreaController(options = {}) {
1373
1477
  }
1374
1478
 
1375
1479
  // src/main/services/views/devtools-tabs.ts
1376
- var DEVTOOLS_KEPT_VIEW_IDS = ["elements", "console", "network"];
1480
+ var DEVTOOLS_KEPT_VIEW_IDS = ["elements", "console", "network", "sources"];
1377
1481
  function buildCustomizeTabsScript(keptIds = DEVTOOLS_KEPT_VIEW_IDS) {
1378
1482
  const NAME_MAP = {
1379
1483
  elements: ["Elements", "\u5143\u7D20"],
1380
1484
  console: ["Console", "\u63A7\u5236\u53F0"],
1381
- network: ["Network", "\u7F51\u7EDC"]
1485
+ network: ["Network", "\u7F51\u7EDC"],
1486
+ sources: ["Sources", "\u6765\u6E90", "\u6E90\u4EE3\u7801"]
1382
1487
  };
1383
1488
  const keepNames = keptIds.flatMap((id) => NAME_MAP[id] ?? [id]);
1384
1489
  const keepIdsJson = JSON.stringify(JSON.stringify([...keptIds]));
1385
1490
  const keepNamesJson = JSON.stringify(JSON.stringify(keepNames));
1491
+ const srcNamesJson = JSON.stringify(JSON.stringify(NAME_MAP.sources));
1492
+ const netNamesJson = JSON.stringify(JSON.stringify(NAME_MAP.network));
1386
1493
  return `(function(){try{
1387
1494
  var KEEPID = new Set(JSON.parse(${keepIdsJson}));
1388
1495
  var KEEPNAME = new Set(JSON.parse(${keepNamesJson}));
1496
+ var SRCNAME = new Set(JSON.parse(${srcNamesJson}));
1497
+ var NETNAME = new Set(JSON.parse(${netNamesJson}));
1498
+ var WANT_SOURCES_LAST = KEEPID.has('sources');
1389
1499
  // (1) Locale infobar \u2014 official host-preference suppression (NOT localStorage).
1390
1500
  try { var IFH=globalThis.InspectorFrontendHost; if(IFH&&typeof IFH.setPreference==='function'){ IFH.setPreference('disable-locale-info-bar','true'); } }catch(_){}
1391
1501
  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; }
@@ -1413,9 +1523,18 @@ function buildCustomizeTabsScript(keptIds = DEVTOOLS_KEPT_VIEW_IDS) {
1413
1523
  handled=true;
1414
1524
  }catch(_){} }
1415
1525
  // fallback: registrations not enumerable -> at least clear the bar by id
1416
- if(!handled){ var FALL=['timeline','resources','heap-profiler','sources','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(_){} } }
1526
+ 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(_){} } }
1417
1527
  } else { domFallback(); }
1418
1528
  })();
1529
+ // Keep Sources LAST in the bar (after Network). Default DevTools order puts
1530
+ // Sources between Console and Network; nudge it to just after Network. Runs in
1531
+ // BOTH paths (registry removal leaves the kept tabs in default order). Self-
1532
+ // stable: once Sources follows Network the condition no longer holds.
1533
+ if(WANT_SOURCES_LAST){ var ro=0,rt=setInterval(function(){ ro++; try{
1534
+ var tabs=deepCollect('[role="tab"]'),src=null,net=null;
1535
+ 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]; }
1536
+ if(src&&net&&src.parentElement&&src.parentElement===net.parentElement&&(src.compareDocumentPosition(net)&Node.DOCUMENT_POSITION_FOLLOWING)){ net.parentElement.insertBefore(src,net.nextSibling); }
1537
+ }catch(_){} if(ro>120)clearInterval(rt); },60); }
1419
1538
  // DOM fallback: only if the ESM module couldn't be resolved at all.
1420
1539
  function domFallback(){ var cachedBar=null;
1421
1540
  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; }
@@ -1670,10 +1789,19 @@ function installElementsForward(deps) {
1670
1789
  function primeGuest(wc) {
1671
1790
  if (!ensureGuestDebugger(wc)) return;
1672
1791
  wireGuestEvents(wc);
1673
- for (const domain of ["DOM.enable", "CSS.enable", "Overlay.enable"]) {
1674
- wc.debugger.sendCommand(domain).catch(() => {
1675
- });
1792
+ void enableGuestDomains(wc);
1793
+ }
1794
+ async function enableGuestDomains(wc) {
1795
+ wc.debugger.sendCommand("CSS.enable").catch(() => {
1796
+ });
1797
+ try {
1798
+ await wc.debugger.sendCommand("DOM.enable");
1799
+ } catch {
1800
+ return;
1676
1801
  }
1802
+ if (disposed || !isActiveWcId(wc.id)) return;
1803
+ wc.debugger.sendCommand("Overlay.enable").catch(() => {
1804
+ });
1677
1805
  }
1678
1806
  function routeCommand(cmd) {
1679
1807
  const wc = activeRenderWc();
@@ -1693,6 +1821,10 @@ function installElementsForward(deps) {
1693
1821
  replyError(cmd, "stale render generation");
1694
1822
  return;
1695
1823
  }
1824
+ if (cmd.method === "Overlay.disable" && isActiveWcId(cmdWcId)) {
1825
+ wc.debugger.sendCommand("Overlay.enable").catch(() => {
1826
+ });
1827
+ }
1696
1828
  replyResult(cmd, result);
1697
1829
  }).catch((err) => {
1698
1830
  if (disposed) return;
@@ -8489,10 +8621,35 @@ function setupSimulatorCurrentPage(host, options) {
8489
8621
  import { readFileSync } from "node:fs";
8490
8622
  import path17 from "node:path";
8491
8623
  var DEFAULT_SOURCE_PATH = "dist/render-host/render-inspect.js";
8624
+ var HIGHLIGHT_CONFIG = {
8625
+ showInfo: true,
8626
+ showRulers: false,
8627
+ showExtensionLines: false,
8628
+ contentColor: { r: 111, g: 168, b: 220, a: 0.66 },
8629
+ paddingColor: { r: 147, g: 196, b: 125, a: 0.55 },
8630
+ borderColor: { r: 255, g: 229, b: 153, a: 0.66 },
8631
+ marginColor: { r: 246, g: 178, b: 107, a: 0.66 }
8632
+ };
8633
+ var HOVER_OBJECT_GROUP = "render-inspect-hover";
8634
+ var ENABLE_HANDSHAKE_TIMEOUT_MS = 500;
8635
+ function withTimeout(p, ms) {
8636
+ return new Promise((resolve) => {
8637
+ const timer = setTimeout(resolve, ms);
8638
+ p.then(() => {
8639
+ clearTimeout(timer);
8640
+ resolve();
8641
+ }, () => {
8642
+ clearTimeout(timer);
8643
+ resolve();
8644
+ });
8645
+ });
8646
+ }
8492
8647
  function createRenderInspector(options = {}) {
8493
8648
  const loadSource = options.loadSource ?? (() => readFileSync(path17.join(devtoolsPackageRoot, DEFAULT_SOURCE_PATH), "utf8"));
8494
8649
  let cachedSource = null;
8495
8650
  const injected = /* @__PURE__ */ new Set();
8651
+ const selfAttached = /* @__PURE__ */ new Set();
8652
+ const enablePromises = /* @__PURE__ */ new Map();
8496
8653
  function source() {
8497
8654
  if (cachedSource === null) cachedSource = loadSource();
8498
8655
  return cachedSource;
@@ -8514,10 +8671,14 @@ function createRenderInspector(options = {}) {
8514
8671
  return false;
8515
8672
  }
8516
8673
  injected.add(wc.id);
8674
+ const forget = () => {
8675
+ injected.delete(wc.id);
8676
+ enablePromises.delete(wc.id);
8677
+ };
8517
8678
  if (options.connections) {
8518
- options.connections.acquire(wc).own(() => injected.delete(wc.id));
8679
+ options.connections.acquire(wc).own(forget);
8519
8680
  } else {
8520
- wc.once("destroyed", () => injected.delete(wc.id));
8681
+ wc.once("destroyed", forget);
8521
8682
  }
8522
8683
  return true;
8523
8684
  }
@@ -8536,22 +8697,97 @@ function createRenderInspector(options = {}) {
8536
8697
  async function highlight(wc, sid) {
8537
8698
  if (wc.isDestroyed()) return null;
8538
8699
  if (!await ensureInjected(wc)) return null;
8700
+ let inspection;
8539
8701
  try {
8540
8702
  const result = await wc.executeJavaScript(
8541
8703
  `window.__diminaRenderInspect ? window.__diminaRenderInspect.highlightElement(${JSON.stringify(sid)}) : null`
8542
8704
  );
8543
- return result ?? null;
8705
+ inspection = result ?? null;
8544
8706
  } catch {
8545
8707
  return null;
8546
8708
  }
8709
+ if (!inspection) return null;
8710
+ await drawNativeHighlight(wc, sid).catch(() => {
8711
+ });
8712
+ return inspection;
8713
+ }
8714
+ async function drawNativeHighlight(wc, sid) {
8715
+ if (wc.isDestroyed()) return;
8716
+ if (!ensureGuestDebugger(wc)) return;
8717
+ await withTimeout(ensureDomainsEnabled(wc), ENABLE_HANDSHAKE_TIMEOUT_MS);
8718
+ if (wc.isDestroyed()) return;
8719
+ const expression = `window.__diminaRenderInspect && window.__diminaRenderInspect.elementFor(${JSON.stringify(sid)})`;
8720
+ try {
8721
+ const evaluated = await wc.debugger.sendCommand("Runtime.evaluate", {
8722
+ expression,
8723
+ returnByValue: false,
8724
+ objectGroup: HOVER_OBJECT_GROUP
8725
+ });
8726
+ const objectId = evaluated?.result?.objectId;
8727
+ if (!objectId) return;
8728
+ await wc.debugger.sendCommand("Overlay.highlightNode", {
8729
+ objectId,
8730
+ highlightConfig: HIGHLIGHT_CONFIG
8731
+ });
8732
+ } finally {
8733
+ wc.debugger.sendCommand("Runtime.releaseObjectGroup", { objectGroup: HOVER_OBJECT_GROUP }).catch(() => {
8734
+ });
8735
+ }
8736
+ }
8737
+ function ensureGuestDebugger(wc) {
8738
+ try {
8739
+ if (wc.debugger.isAttached()) return true;
8740
+ } catch {
8741
+ return false;
8742
+ }
8743
+ try {
8744
+ wc.debugger.attach("1.3");
8745
+ trackSelfAttached(wc);
8746
+ return true;
8747
+ } catch {
8748
+ try {
8749
+ return wc.debugger.isAttached();
8750
+ } catch {
8751
+ return false;
8752
+ }
8753
+ }
8754
+ }
8755
+ function trackSelfAttached(wc) {
8756
+ if (selfAttached.has(wc.id)) return;
8757
+ selfAttached.add(wc.id);
8758
+ const detach = () => {
8759
+ selfAttached.delete(wc.id);
8760
+ try {
8761
+ if (!wc.isDestroyed() && wc.debugger.isAttached()) wc.debugger.detach();
8762
+ } catch {
8763
+ }
8764
+ };
8765
+ if (options.connections) options.connections.acquire(wc).own(detach);
8766
+ else {
8767
+ try {
8768
+ wc.once("destroyed", () => selfAttached.delete(wc.id));
8769
+ } catch {
8770
+ }
8771
+ }
8772
+ }
8773
+ function ensureDomainsEnabled(wc) {
8774
+ const cached = enablePromises.get(wc.id);
8775
+ if (cached) return cached;
8776
+ wc.debugger.sendCommand("CSS.enable").catch(() => {
8777
+ });
8778
+ const handshake = wc.debugger.sendCommand("DOM.enable").then(() => {
8779
+ if (wc.isDestroyed()) return;
8780
+ return wc.debugger.sendCommand("Overlay.enable");
8781
+ }).then(() => void 0).catch(() => {
8782
+ enablePromises.delete(wc.id);
8783
+ });
8784
+ enablePromises.set(wc.id, handshake);
8785
+ return handshake;
8547
8786
  }
8548
8787
  async function unhighlight(wc) {
8549
8788
  if (wc.isDestroyed()) return;
8550
- if (!await ensureInjected(wc)) return;
8551
8789
  try {
8552
- await wc.executeJavaScript(
8553
- "window.__diminaRenderInspect && window.__diminaRenderInspect.unhighlightElement && window.__diminaRenderInspect.unhighlightElement()"
8554
- );
8790
+ await wc.debugger.sendCommand("Overlay.hideHighlight");
8555
8791
  } catch {
8556
8792
  }
8557
8793
  }
@@ -430,14 +430,40 @@ export function installElementsForward(deps) {
430
430
  catch { /* gone */ }
431
431
  });
432
432
  }
433
- /** enable DOM/CSS/Overlay + wire events on a guest. Best-effort. */
433
+ /**
434
+ * enable DOM/CSS/Overlay + wire events on a guest. Best-effort (never throws).
435
+ *
436
+ * `Overlay.enable` is sent ONLY AFTER `DOM.enable` has resolved: Chromium rejects
437
+ * `Overlay.enable` with "DOM should be enabled first" when it arrives before the
438
+ * DOM domain is enabled, and a silently-dropped rejection leaves Overlay disabled
439
+ * so every later `Overlay.highlightNode` fails with "Overlay must be enabled" and
440
+ * the Elements-panel hover highlight never paints. Awaiting DOM.enable first
441
+ * guarantees the correct enable order. CSS.enable has no such dependency and is
442
+ * fire-and-forget alongside.
443
+ */
434
444
  function primeGuest(wc) {
435
445
  if (!ensureGuestDebugger(wc))
436
446
  return;
437
447
  wireGuestEvents(wc);
438
- for (const domain of ['DOM.enable', 'CSS.enable', 'Overlay.enable']) {
439
- wc.debugger.sendCommand(domain).catch(() => { });
448
+ void enableGuestDomains(wc);
449
+ }
450
+ /** Enable the render domains in dependency order. Resolves; never rejects. */
451
+ async function enableGuestDomains(wc) {
452
+ wc.debugger.sendCommand('CSS.enable').catch(() => { });
453
+ try {
454
+ await wc.debugger.sendCommand('DOM.enable');
440
455
  }
456
+ catch {
457
+ // Guest mid-destroy or DOM domain unavailable; Overlay.enable would only
458
+ // re-reject, so stop here rather than firing it out of order.
459
+ return;
460
+ }
461
+ // DOM.enable can settle after the guest was destroyed or the active page
462
+ // switched away (priming always targets the active guest). Re-check before
463
+ // arming Overlay so a stale/dead guest is left untouched.
464
+ if (disposed || !isActiveWcId(wc.id))
465
+ return;
466
+ wc.debugger.sendCommand('Overlay.enable').catch(() => { });
441
467
  }
442
468
  // ── front-end → render command routing ─────────────────────────────────────
443
469
  function routeCommand(cmd) {
@@ -465,6 +491,14 @@ export function installElementsForward(deps) {
465
491
  replyError(cmd, 'stale render generation');
466
492
  return;
467
493
  }
494
+ // The front-end emits `Overlay.disable` on Elements-panel state
495
+ // transitions. Forwarded verbatim it disables the guest's Overlay agent,
496
+ // after which every `Overlay.highlightNode` fails with "Overlay must be
497
+ // enabled" and the hover highlight stops painting. Re-arm it so the next
498
+ // hover paints — only while this guest is still the active one.
499
+ if (cmd.method === 'Overlay.disable' && isActiveWcId(cmdWcId)) {
500
+ wc.debugger.sendCommand('Overlay.enable').catch(() => { });
501
+ }
468
502
  replyResult(cmd, result);
469
503
  })
470
504
  .catch((err) => {