@dimina-kit/devtools 0.3.2-dev.20260525152421 → 0.3.2-dev.20260610082053
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 +43 -40
- package/dist/main/api.d.ts +0 -1
- package/dist/main/api.js +5 -1
- package/dist/main/app/app.d.ts +15 -5
- package/dist/main/app/app.js +247 -110
- package/dist/main/app/bootstrap.d.ts +16 -0
- package/dist/main/app/bootstrap.js +20 -0
- package/dist/main/app/launch.d.ts +8 -4
- package/dist/main/app/launch.js +9 -4
- package/dist/main/index.bundle.js +6607 -2102
- package/dist/main/index.js +9 -1
- package/dist/main/ipc/app.d.ts +1 -1
- package/dist/main/ipc/bridge-router.d.ts +61 -0
- package/dist/main/ipc/bridge-router.js +1300 -0
- package/dist/main/ipc/panels.d.ts +1 -1
- package/dist/main/ipc/panels.js +6 -2
- package/dist/main/ipc/popover.d.ts +1 -1
- package/dist/main/ipc/project-fs.d.ts +57 -0
- package/dist/main/ipc/project-fs.js +448 -0
- package/dist/main/ipc/projects.d.ts +1 -1
- package/dist/main/ipc/session.d.ts +1 -1
- package/dist/main/ipc/settings.d.ts +1 -1
- package/dist/main/ipc/simulator-module.d.ts +6 -0
- package/dist/main/ipc/simulator-module.js +11 -1
- package/dist/main/ipc/simulator.d.ts +2 -2
- package/dist/main/ipc/simulator.js +49 -5
- package/dist/main/ipc/toolbar.d.ts +1 -1
- package/dist/main/ipc/views.d.ts +19 -0
- package/dist/main/ipc/views.js +65 -0
- package/dist/main/runtime/devtools-backend.d.ts +17 -0
- package/dist/main/runtime/devtools-backend.js +49 -0
- package/dist/main/runtime/miniapp-runtime.d.ts +39 -0
- package/dist/main/runtime/miniapp-runtime.js +17 -0
- package/dist/main/services/automation/exec.d.ts +27 -2
- package/dist/main/services/automation/exec.js +37 -3
- package/dist/main/services/automation/handlers/app.js +127 -11
- package/dist/main/services/automation/handlers/page.js +36 -19
- package/dist/main/services/automation/index.js +40 -13
- package/dist/main/services/automation/wait-active-page.d.ts +56 -0
- package/dist/main/services/automation/wait-active-page.js +71 -0
- package/dist/main/services/console-forward/index.d.ts +36 -0
- package/dist/main/services/console-forward/index.js +81 -0
- package/dist/main/services/dimina-resource-server.d.ts +7 -0
- package/dist/main/services/dimina-resource-server.js +99 -0
- package/dist/main/services/elements-forward/index.d.ts +66 -0
- package/dist/main/services/elements-forward/index.js +583 -0
- package/dist/main/services/layout/index.d.ts +23 -1
- package/dist/main/services/layout/index.js +28 -3
- package/dist/main/services/mcp/index.d.ts +1 -0
- package/dist/main/services/mcp/index.js +3 -0
- package/dist/main/services/mcp/server.d.ts +1 -1
- package/dist/main/services/mcp/server.js +1 -1
- package/dist/main/services/mcp/target-manager.d.ts +35 -0
- package/dist/main/services/mcp/target-manager.js +63 -1
- package/dist/main/services/mcp/tools/context-tools.js +15 -1
- package/dist/main/services/module.d.ts +1 -1
- package/dist/main/services/network-forward/index.d.ts +190 -0
- package/dist/main/services/network-forward/index.js +765 -0
- package/dist/main/services/notifications/renderer-notifier.d.ts +11 -0
- package/dist/main/services/notifications/renderer-notifier.js +7 -1
- package/dist/main/services/render-inspect/index.d.ts +41 -0
- package/dist/main/services/render-inspect/index.js +88 -0
- package/dist/main/services/safe-area/index.d.ts +17 -0
- package/dist/main/services/safe-area/index.js +59 -0
- package/dist/main/services/service-host-pool/pool.d.ts +197 -0
- package/dist/main/services/service-host-pool/pool.js +364 -0
- package/dist/main/services/simulator/custom-apis.d.ts +34 -0
- package/dist/main/services/simulator/custom-apis.js +22 -0
- package/dist/main/services/simulator-appdata/index.d.ts +35 -0
- package/dist/main/services/simulator-appdata/index.js +71 -0
- package/dist/main/services/simulator-current-page/index.d.ts +24 -0
- package/dist/main/services/simulator-current-page/index.js +19 -0
- package/dist/main/services/simulator-storage/index.d.ts +37 -2
- package/dist/main/services/simulator-storage/index.js +209 -32
- package/dist/main/services/simulator-storage/service-storage-ops.d.ts +40 -0
- package/dist/main/services/simulator-storage/service-storage-ops.js +67 -0
- package/dist/main/services/simulator-temp-files/fs-channels.js +1 -1
- package/dist/main/services/simulator-temp-files/index.d.ts +1 -1
- package/dist/main/services/simulator-temp-files/index.js +1 -1
- package/dist/main/services/simulator-temp-files/request-handler.js +1 -1
- package/dist/main/services/simulator-wxml/index.d.ts +29 -0
- package/dist/main/services/simulator-wxml/index.js +28 -0
- package/dist/main/services/update/update-manager.d.ts +10 -0
- package/dist/main/services/update/update-manager.js +16 -3
- package/dist/main/services/views/devtools-tabs.d.ts +47 -0
- package/dist/main/services/views/devtools-tabs.js +95 -0
- package/dist/main/services/views/view-manager.d.ts +142 -4
- package/dist/main/services/views/view-manager.js +909 -17
- package/dist/main/services/workbench-context.d.ts +66 -1
- package/dist/main/services/workbench-context.js +6 -1
- package/dist/main/services/workspace/workspace-service.d.ts +8 -0
- package/dist/main/services/workspace/workspace-service.js +12 -0
- package/dist/main/utils/ipc-registry.d.ts +11 -2
- package/dist/main/utils/ipc-registry.js +63 -3
- package/dist/main/utils/paths.d.ts +15 -0
- package/dist/main/utils/paths.js +19 -0
- package/dist/main/utils/sender-policy.d.ts +9 -3
- package/dist/main/utils/sender-policy.js +18 -4
- package/dist/main/utils/theme.d.ts +1 -1
- package/dist/main/utils/theme.js +1 -1
- package/dist/main/windows/main-window/create.d.ts +0 -6
- package/dist/main/windows/main-window/create.js +11 -59
- package/dist/main/windows/main-window/events.d.ts +1 -1
- package/dist/main/windows/main-window/events.js +1 -1
- package/dist/main/windows/service-host-window/create.d.ts +52 -0
- package/dist/main/windows/service-host-window/create.js +112 -0
- package/dist/native-host/common/common.js +368 -0
- package/dist/native-host/container/pageFrame.css +1 -0
- package/dist/native-host/render/render.js +9334 -0
- package/dist/native-host/service/service.js +6 -0
- package/dist/preload/index.d.ts +2 -0
- package/dist/preload/index.js +6 -0
- package/dist/preload/instrumentation/app-data.d.ts +18 -20
- package/dist/preload/instrumentation/app-data.js +23 -160
- package/dist/preload/instrumentation/wxml-extract.d.ts +1 -1
- package/dist/preload/instrumentation/wxml-extract.js +1 -1
- package/dist/preload/runtime/bridge.d.ts +3 -8
- package/dist/preload/runtime/bridge.js +2 -32
- package/dist/preload/runtime/custom-apis.js +20 -17
- package/dist/preload/runtime/host-toolbar-advertiser.d.ts +27 -0
- package/dist/preload/runtime/host-toolbar-advertiser.js +55 -0
- package/dist/preload/runtime/main-api-runner.d.ts +9 -0
- package/dist/preload/runtime/main-api-runner.js +85 -0
- package/dist/preload/runtime/native-host.d.ts +43 -0
- package/dist/preload/runtime/native-host.js +83 -0
- package/dist/preload/shared/expose.d.ts +10 -5
- package/dist/preload/shared/expose.js +29 -11
- package/dist/preload/shared/sid-registry.d.ts +11 -0
- package/dist/preload/shared/sid-registry.js +38 -0
- package/dist/preload/windows/host-toolbar.cjs +161 -0
- package/dist/preload/windows/host-toolbar.cjs.map +7 -0
- package/dist/preload/windows/host-toolbar.d.ts +2 -0
- package/dist/preload/windows/host-toolbar.js +12 -0
- package/dist/preload/windows/main.cjs +1939 -0
- package/dist/preload/windows/main.cjs.map +4 -4
- package/dist/preload/windows/simulator.cjs +925 -0
- package/dist/preload/windows/simulator.cjs.map +7 -0
- package/dist/preload/windows/simulator.js +243 -573
- package/dist/render-host/pageFrame.html +29 -0
- package/dist/render-host/preload.cjs +154 -0
- package/dist/render-host/render-inspect.js +295 -0
- package/dist/renderer/assets/abap-DiwvWnMr.js +2 -0
- package/dist/renderer/assets/apex-CmtZjKlf.js +2 -0
- package/dist/renderer/assets/azcli-DL2My_i-.js +2 -0
- package/dist/renderer/assets/bat-B-nC98wG.js +2 -0
- package/dist/renderer/assets/bicep-Ju5MwOgh.js +3 -0
- package/dist/renderer/assets/cameligo-8Eu1TyBr.js +2 -0
- package/dist/renderer/assets/chunk-DPI0Y_ll.js +1 -0
- package/dist/renderer/assets/clojure-u-RpMkH3.js +2 -0
- package/dist/renderer/assets/codicon-ngg6Pgfi.ttf +0 -0
- package/dist/renderer/assets/coffee-CdA7bbTe.js +2 -0
- package/dist/renderer/assets/constants-CLAQX6VZ.js +2 -0
- package/dist/renderer/assets/cpp-CzNFP8ks.js +2 -0
- package/dist/renderer/assets/csharp-j1LThmcE.js +2 -0
- package/dist/renderer/assets/csp-CLRC61y6.js +2 -0
- package/dist/renderer/assets/css-r6rC_7P2.js +4 -0
- package/dist/renderer/assets/css.worker-DyDu5ynT.js +90 -0
- package/dist/renderer/assets/cssMode-BCBgxrnA.js +2 -0
- package/dist/renderer/assets/cypher-CW08XVUh.js +2 -0
- package/dist/renderer/assets/dart-Cs9aL5T_.js +2 -0
- package/dist/renderer/assets/dockerfile-BWM0M184.js +2 -0
- package/dist/renderer/assets/ecl-MJJuer5P.js +2 -0
- package/dist/renderer/assets/editor-CWI9TnMM.css +1 -0
- package/dist/renderer/assets/editor.api2-BTaYmRq7.js +873 -0
- package/dist/renderer/assets/editor.worker-Dpt_xPrs.js +27 -0
- package/dist/renderer/assets/elixir-D2AIuXqn.js +2 -0
- package/dist/renderer/assets/flow9-B2H24giC.js +2 -0
- package/dist/renderer/assets/freemarker2-DGDGzimW.js +4 -0
- package/dist/renderer/assets/fsharp-CMk2OIJN.js +2 -0
- package/dist/renderer/assets/go-BrMkuJg0.js +2 -0
- package/dist/renderer/assets/graphql-PSR1UKGv.js +2 -0
- package/dist/renderer/assets/handlebars-bOHhw3EG.js +2 -0
- package/dist/renderer/assets/hcl-DAQrbDOW.js +2 -0
- package/dist/renderer/assets/html-BMyuX0_c.js +2 -0
- package/dist/renderer/assets/html.worker-IWNXhnIC.js +503 -0
- package/dist/renderer/assets/htmlMode-DR4kXJ7l.js +2 -0
- package/dist/renderer/assets/index-CcJKzatF.js +50 -0
- package/dist/renderer/assets/ini-0TG5BxW0.js +2 -0
- package/dist/renderer/assets/input-QGx6FFX1.js +2 -0
- package/dist/renderer/assets/ipc-transport-BT4QYQqu.css +1 -0
- package/dist/renderer/assets/{ipc-transport-BzPzxPwf.js → ipc-transport-DvZvwK-Z.js} +5 -5
- package/dist/renderer/assets/java-rgorz17v.js +2 -0
- package/dist/renderer/assets/javascript-Bu-tl1P7.js +2 -0
- package/dist/renderer/assets/json.worker-c4h5s80L.js +59 -0
- package/dist/renderer/assets/jsonMode-MQV_ffLR.js +8 -0
- package/dist/renderer/assets/julia-C8VMdHm8.js +2 -0
- package/dist/renderer/assets/kotlin-AhhC4Qhm.js +2 -0
- package/dist/renderer/assets/less-Cgca25AP.js +3 -0
- package/dist/renderer/assets/lexon-D0GHdBaw.js +2 -0
- package/dist/renderer/assets/liquid-ClkfOvhB.js +2 -0
- package/dist/renderer/assets/lspLanguageFeatures-Bke_4Ie9.js +5 -0
- package/dist/renderer/assets/lua-DmRsNG-P.js +2 -0
- package/dist/renderer/assets/m3-BgL5dNKT.js +2 -0
- package/dist/renderer/assets/markdown-BuJfycGS.js +2 -0
- package/dist/renderer/assets/mdx-RZdUskPu.js +2 -0
- package/dist/renderer/assets/mips-C9m_93PR.js +2 -0
- package/dist/renderer/assets/monaco.contribution-B7rUMaEv.js +3 -0
- package/dist/renderer/assets/msdax-CpFHC9OI.js +2 -0
- package/dist/renderer/assets/mysql-qFvltsqN.js +2 -0
- package/dist/renderer/assets/objective-c-Bnmr858J.js +2 -0
- package/dist/renderer/assets/pascal-WP0_D5AO.js +2 -0
- package/dist/renderer/assets/pascaligo-Blom4Rij.js +2 -0
- package/dist/renderer/assets/perl-B-vk8g64.js +2 -0
- package/dist/renderer/assets/pgsql-Cgvz6v67.js +2 -0
- package/dist/renderer/assets/php-8a3Lrw9m.js +2 -0
- package/dist/renderer/assets/pla-DuFqEZ8V.js +2 -0
- package/dist/renderer/assets/popover-BOH1If1J.js +2 -0
- package/dist/renderer/assets/postiats-DkLtSgkp.js +2 -0
- package/dist/renderer/assets/powerquery-BJ1aNepW.js +2 -0
- package/dist/renderer/assets/powershell-rE98k687.js +2 -0
- package/dist/renderer/assets/protobuf-CUheFacr.js +3 -0
- package/dist/renderer/assets/pug-LDcAMD8w.js +2 -0
- package/dist/renderer/assets/python-Cobrm_-8.js +2 -0
- package/dist/renderer/assets/qsharp-DUKSQoR1.js +2 -0
- package/dist/renderer/assets/r-D-QApv87.js +2 -0
- package/dist/renderer/assets/razor-BEUIQFNJ.js +2 -0
- package/dist/renderer/assets/redis-SXdDyWR9.js +2 -0
- package/dist/renderer/assets/redshift-Y6lsCryn.js +2 -0
- package/dist/renderer/assets/restructuredtext-edObr9a8.js +2 -0
- package/dist/renderer/assets/ruby-CNnUfF-8.js +2 -0
- package/dist/renderer/assets/rust-IHUZWzBr.js +2 -0
- package/dist/renderer/assets/sb-DrUvY44N.js +2 -0
- package/dist/renderer/assets/scala-B4hbXGLM.js +2 -0
- package/dist/renderer/assets/scheme-BGrd12j3.js +2 -0
- package/dist/renderer/assets/scss-x5G1ES4U.js +4 -0
- package/dist/renderer/assets/select-C7JQ8G5N.js +2 -0
- package/dist/renderer/assets/settings-DpAbTJyF.js +2 -0
- package/dist/renderer/assets/{settings-api-BXCw9vOZ.js → settings-api-RD2IZF53.js} +2 -2
- package/dist/renderer/assets/shell-DOehe2Y8.js +2 -0
- package/dist/renderer/assets/solidity-BeRvcwWV.js +2 -0
- package/dist/renderer/assets/sophia-DZbkUNjy.js +2 -0
- package/dist/renderer/assets/sparql-B7_oi5-h.js +2 -0
- package/dist/renderer/assets/sql-CTlsFWVE.js +2 -0
- package/dist/renderer/assets/st-DJVEJdPE.js +2 -0
- package/dist/renderer/assets/swift-CwhT3fYa.js +2 -0
- package/dist/renderer/assets/systemverilog-BQN63pkN.js +2 -0
- package/dist/renderer/assets/tcl-DqwfpskA.js +2 -0
- package/dist/renderer/assets/ts.worker-BwM5Ha3L.js +67720 -0
- package/dist/renderer/assets/tsMode-BHa7dl9v.js +12 -0
- package/dist/renderer/assets/twig-BiyenUgc.js +2 -0
- package/dist/renderer/assets/typescript-15aT0Yj8.js +2 -0
- package/dist/renderer/assets/typespec-CWOJribt.js +2 -0
- package/dist/renderer/assets/vb-Cq5F87m3.js +2 -0
- package/dist/renderer/assets/wgsl-BAvW2lVr.js +299 -0
- package/dist/renderer/assets/workbenchSettings-CNJX4Qna.js +8 -0
- package/dist/renderer/assets/workers-CRBS0W-v.js +2 -0
- package/dist/renderer/assets/xml-bR2Zh-HV.js +2 -0
- package/dist/renderer/assets/yaml-Bdk54Qrb.js +2 -0
- package/dist/renderer/entries/main/index.html +29 -6
- package/dist/renderer/entries/popover/index.html +7 -6
- package/dist/renderer/entries/settings/index.html +6 -5
- package/dist/renderer/entries/workbench-settings/index.html +6 -5
- package/dist/service-host/preload.cjs +254 -0
- package/dist/service-host/service.html +10 -0
- package/dist/service-host/sourcemap-rewrite.cjs +58 -0
- package/dist/service-host/sync-api-patch.js +20 -0
- package/dist/service-host/sync-impls/menu-button.js +16 -0
- package/dist/service-host/sync-impls/storage.js +50 -0
- package/dist/service-host/sync-impls/system-info.js +54 -0
- package/dist/shared/appdata-accumulator.d.ts +95 -0
- package/dist/shared/appdata-accumulator.js +213 -0
- package/dist/shared/bridge-channels.d.ts +364 -0
- package/dist/shared/bridge-channels.js +54 -0
- package/dist/shared/constants.d.ts +0 -4
- package/dist/shared/constants.js +0 -4
- package/dist/shared/ipc-channels.d.ts +108 -1
- package/dist/shared/ipc-channels.js +127 -10
- package/dist/shared/ipc-schemas.d.ts +65 -2
- package/dist/shared/ipc-schemas.js +79 -3
- package/dist/shared/open-in-editor.d.ts +71 -0
- package/dist/shared/open-in-editor.js +138 -0
- package/dist/shared/simulator-api-metadata.d.ts +21 -0
- package/dist/shared/simulator-api-metadata.js +23 -0
- package/dist/shared/types.d.ts +2 -2
- package/dist/shared/vpath.d.ts +42 -0
- package/dist/shared/vpath.js +105 -0
- package/dist/simulator/assets/device-shell-DsdcNEkt.css +1 -0
- package/dist/simulator/assets/device-shell-vwYSOVrh.js +2 -0
- package/dist/simulator/assets/jsx-runtime-P6ZegaOf.js +2 -0
- package/dist/simulator/assets/simulator-DddliUL5.js +10 -0
- package/dist/simulator/assets/simulator-mini-app-BDNu5n26.js +2 -0
- package/dist/simulator/device-shell/menu-button-geometry.js +8 -0
- package/dist/simulator/simulator.html +2 -112
- package/package.json +9 -9
- package/dist/main/utils/disposable.d.ts +0 -18
- package/dist/main/utils/disposable.js +0 -67
- package/dist/renderer/assets/constants-DI382mTP.js +0 -2
- package/dist/renderer/assets/index-4lkyF644.js +0 -46
- package/dist/renderer/assets/input-D4byFOLS.js +0 -2
- package/dist/renderer/assets/ipc-transport-DqbRdZNO.css +0 -1
- package/dist/renderer/assets/popover-cxKrg_os.js +0 -2
- package/dist/renderer/assets/select-BgSkkdHW.js +0 -2
- package/dist/renderer/assets/settings-oD2K1jdT.js +0 -2
- package/dist/renderer/assets/workbenchSettings-CTNBpTft.js +0 -8
- package/dist/simulator/assets/simulator-CVOFTpSj.js +0 -9
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import { WebContentsView, webContents } from 'electron';
|
|
1
|
+
import { ipcMain, shell, WebContentsView, webContents } from 'electron';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { mainPreloadPath } from '../../utils/paths.js';
|
|
4
|
-
import { applyNavigationHardening } from '../../windows/navigation-hardening.js';
|
|
3
|
+
import { cjsSiblingPreloadPath, hostToolbarPreloadPath, mainPreloadPath } from '../../utils/paths.js';
|
|
4
|
+
import { applyNavigationHardening, handleWindowOpenExternal, } from '../../windows/navigation-hardening.js';
|
|
5
|
+
import { SimulatorCustomApiBridgeChannel } from '../../../shared/ipc-channels.js';
|
|
6
|
+
import { OPEN_IN_EDITOR_SCHEME, decodeOpenInEditorUrl, resourceUrlToProjectRelativePath, } from '../../../shared/open-in-editor.js';
|
|
7
|
+
import { createSafeAreaController } from '../safe-area/index.js';
|
|
8
|
+
import { buildCustomizeTabsScript } from './devtools-tabs.js';
|
|
9
|
+
import { installElementsForward } from '../elements-forward/index.js';
|
|
5
10
|
import * as layout from '../layout/index.js';
|
|
11
|
+
import { handleCustomApiBridgeRequest, } from '../simulator/custom-apis.js';
|
|
6
12
|
import { getDefaultTab } from '../workbench-context.js';
|
|
7
13
|
/**
|
|
8
14
|
* Build a ViewManager bound to the given context. The returned object is the
|
|
@@ -15,14 +21,71 @@ export function createViewManager(ctx) {
|
|
|
15
21
|
// Resolve once: full WorkbenchContext always provides headerHeight; partial
|
|
16
22
|
// test contexts may omit it, in which case fall back to the default 40.
|
|
17
23
|
const headerHeight = ctx.headerHeight ?? 40;
|
|
24
|
+
// CSS env(safe-area-inset-*) simulation for render-host guests (per device).
|
|
25
|
+
// Driven from did-attach-webview below; re-pushed on device change via
|
|
26
|
+
// reapplySafeArea. Torn down in disposeAll.
|
|
27
|
+
const safeArea = createSafeAreaController({ connections: ctx.connections });
|
|
18
28
|
// ── Private mutable state ───────────────────────────────────────────────
|
|
19
29
|
let simulatorView = null;
|
|
20
30
|
let simulatorViewAdded = false;
|
|
31
|
+
// NATIVE-HOST ONLY: the simulator content WebContentsView (the DeviceShell
|
|
32
|
+
// host). In the default path the simulator is a renderer `<webview>` and this
|
|
33
|
+
// stays null. Positioned in the simulator panel region (left of the splitter)
|
|
34
|
+
// while `simulatorView` above hosts its DevTools in the right panel region.
|
|
35
|
+
let nativeSimulatorView = null;
|
|
36
|
+
let nativeSimulatorViewAdded = false;
|
|
37
|
+
// Model A: the renderer's measured inner-screen rect is the SOLE authority for
|
|
38
|
+
// the native simulator WCV bounds (see docs/simulator-render-architecture.md).
|
|
39
|
+
// Cache the last rect so a report that lands before attachNativeSimulator (the
|
|
40
|
+
// project-open ordering) is not lost — attach replays it instead of using a
|
|
41
|
+
// coarse panel-size fallback (which raced and caused the clip/surround flips).
|
|
42
|
+
let lastRendererRect = null;
|
|
43
|
+
// NATIVE-HOST ONLY. The `ipcMain.on` listener that services the
|
|
44
|
+
// `__diminaCustomApis` bridge for the current native simulator webContents
|
|
45
|
+
// (see `attachNativeCustomApiBridge`). Tracked so we can remove it before
|
|
46
|
+
// tearing down / re-attaching the view (otherwise listeners leak across
|
|
47
|
+
// relaunch cycles).
|
|
48
|
+
let nativeCustomApiBridgeHandler = null;
|
|
49
|
+
// NATIVE-HOST ONLY. The current device zoom as a factor (zoomPercent/100),
|
|
50
|
+
// last reported by the renderer via setNativeSimulatorViewBounds. Stored so
|
|
51
|
+
// nested render-host `<webview>` guests attached AFTER a zoom change still
|
|
52
|
+
// pick up the correct scale in `did-attach-webview`. Defaults to 1 (100%).
|
|
53
|
+
let currentZoomFactor = 1;
|
|
21
54
|
let settingsView = null;
|
|
22
55
|
let settingsViewAdded = false;
|
|
23
56
|
let popoverView = null;
|
|
24
57
|
let lastSimWidth = 375;
|
|
25
58
|
let simulatorWebContentsId = null;
|
|
59
|
+
// NATIVE-HOST ONLY. The webContents the right-panel Chrome DevTools front-end
|
|
60
|
+
// currently inspects. We point it at the SERVICE HOST (logic layer) — the
|
|
61
|
+
// hidden BrowserWindow where the mini-app's page code runs (`console.log`,
|
|
62
|
+
// `wx.request`, Sources/Network(fetch) all live there). The UI/view layer
|
|
63
|
+
// (Elements/Styles/WXML tree) is served separately by the native WXML panel +
|
|
64
|
+
// render-guest highlight chain (`simulator-storage`/`simulator-wxml`), so a
|
|
65
|
+
// single DevTools front-end is enough. The service window can be swapped on
|
|
66
|
+
// respawn (pre-warm pool recycles it), so this is re-resolved fresh via
|
|
67
|
+
// `ctx.bridge.getServiceWc()` and re-pointed on every render-side event.
|
|
68
|
+
let nativeDevtoolsSourceWc = null;
|
|
69
|
+
let unsubscribeNativeRenderEvents = null;
|
|
70
|
+
// Disposer for the Elements-forward feature (routes the front-end's Elements
|
|
71
|
+
// CDP traffic at the active render guest). Installed in
|
|
72
|
+
// `attachNativeSimulatorDevtoolsHost`, stopped on detach / host destroyed.
|
|
73
|
+
let stopElementsForward = null;
|
|
74
|
+
let nativeDevtoolsRetryTimer = null;
|
|
75
|
+
let nativeDevtoolsRetryToken = 0;
|
|
76
|
+
// Renderer-driven overlay bounds for the simulator DevTools view. When
|
|
77
|
+
// non-null this takes precedence over the legacy layout computed from the
|
|
78
|
+
// window content size. A zero-area rectangle means "hide" — the overlay is
|
|
79
|
+
// removed from the contentView but its WebContents stays alive.
|
|
80
|
+
let simulatorBoundsOverride = null;
|
|
81
|
+
// ── Host-controllable toolbar WebContentsView ───────────────────────────
|
|
82
|
+
// A strip above the devtools header that the downstream host loads its own
|
|
83
|
+
// content into and fully controls. Bounds come from a renderer DOM anchor
|
|
84
|
+
// (forward anchor, like the simulator DevTools overlay); its height is
|
|
85
|
+
// dynamic via a reverse size-advertiser the toolbar's own renderer drives.
|
|
86
|
+
let hostToolbarView = null;
|
|
87
|
+
let hostToolbarPreloadOverride = null;
|
|
88
|
+
let hostToolbarViewAdded = false;
|
|
26
89
|
// ── Internal helpers ────────────────────────────────────────────────────
|
|
27
90
|
function destroyViewInternal(view) {
|
|
28
91
|
if (!view)
|
|
@@ -43,9 +106,160 @@ export function createViewManager(ctx) {
|
|
|
43
106
|
function applySimulatorBounds(simWidth) {
|
|
44
107
|
if (!simulatorView || ctx.windows.mainWindow.isDestroyed())
|
|
45
108
|
return;
|
|
109
|
+
if (simulatorBoundsOverride) {
|
|
110
|
+
simulatorView.setBounds(simulatorBoundsOverride);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
46
113
|
const [w = 0, h = 0] = ctx.windows.mainWindow.getContentSize();
|
|
47
114
|
simulatorView.setBounds(layout.computeSimulatorBounds(w, h, simWidth, headerHeight));
|
|
48
115
|
}
|
|
116
|
+
// Renderer publishes width/height = 0 to mean "hide overlay" — the
|
|
117
|
+
// surrounding React panel is unmounted/collapsed. We keep the cached
|
|
118
|
+
// value (so future republishes win over legacy layout) but remove the
|
|
119
|
+
// child view from the contentView until a non-empty rect arrives.
|
|
120
|
+
function isHidden(b) {
|
|
121
|
+
return b.width <= 0 || b.height <= 0;
|
|
122
|
+
}
|
|
123
|
+
function setSimulatorDevtoolsBounds(bounds) {
|
|
124
|
+
simulatorBoundsOverride = bounds;
|
|
125
|
+
if (!simulatorView || simulatorView.webContents.isDestroyed())
|
|
126
|
+
return;
|
|
127
|
+
if (ctx.windows.mainWindow.isDestroyed())
|
|
128
|
+
return;
|
|
129
|
+
if (isHidden(bounds)) {
|
|
130
|
+
if (simulatorViewAdded) {
|
|
131
|
+
try {
|
|
132
|
+
ctx.windows.mainWindow.contentView.removeChildView(simulatorView);
|
|
133
|
+
}
|
|
134
|
+
catch { /* already removed */ }
|
|
135
|
+
simulatorViewAdded = false;
|
|
136
|
+
}
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (!simulatorViewAdded) {
|
|
140
|
+
ctx.windows.mainWindow.contentView.addChildView(simulatorView);
|
|
141
|
+
simulatorViewAdded = true;
|
|
142
|
+
}
|
|
143
|
+
simulatorView.setBounds(bounds);
|
|
144
|
+
}
|
|
145
|
+
// ── Host-controllable toolbar WebContentsView ───────────────────────────
|
|
146
|
+
// Lazily create the host-toolbar view. Mirrors `showSettings` for the
|
|
147
|
+
// webPreferences shape and the native simulator for nav hardening +
|
|
148
|
+
// background color (the host may load arbitrary URLs / content). Idempotent.
|
|
149
|
+
function ensureHostToolbarView() {
|
|
150
|
+
if (hostToolbarView && !hostToolbarView.webContents.isDestroyed()) {
|
|
151
|
+
return hostToolbarView;
|
|
152
|
+
}
|
|
153
|
+
// Rebuilding after the host destroyed the underlying webContents: detach the
|
|
154
|
+
// dead view from the contentView and reset the added-flag so the new view
|
|
155
|
+
// gets re-mounted (otherwise the `hostToolbarViewAdded` guard would skip the
|
|
156
|
+
// addChildView and the toolbar would silently disappear).
|
|
157
|
+
if (hostToolbarView && hostToolbarViewAdded) {
|
|
158
|
+
try {
|
|
159
|
+
ctx.windows.mainWindow.contentView.removeChildView(hostToolbarView);
|
|
160
|
+
}
|
|
161
|
+
catch { /* already removed */ }
|
|
162
|
+
hostToolbarViewAdded = false;
|
|
163
|
+
}
|
|
164
|
+
const view = new WebContentsView({
|
|
165
|
+
webPreferences: {
|
|
166
|
+
nodeIntegration: false,
|
|
167
|
+
contextIsolation: true,
|
|
168
|
+
sandbox: false,
|
|
169
|
+
// Default: the reverse size-advertiser preload that measures the host
|
|
170
|
+
// content's intrinsic height and posts it on the advertise channel so
|
|
171
|
+
// main reserves exactly that strip height (dynamic height via
|
|
172
|
+
// ViewAnchor). When the host-shell supplies its own toolbar preload
|
|
173
|
+
// (workbench config.toolbar.preloadPath), use that instead — the host
|
|
174
|
+
// owns the bridge and may install the advertiser itself.
|
|
175
|
+
preload: hostToolbarPreloadOverride ?? hostToolbarPreloadPath,
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
hostToolbarView = view;
|
|
179
|
+
// Paint the surface a neutral color so growing the reserved strip never
|
|
180
|
+
// flashes white before the host content paints (mirrors the native
|
|
181
|
+
// simulator's setBackgroundColor anti-flash).
|
|
182
|
+
try {
|
|
183
|
+
view.setBackgroundColor('#121212');
|
|
184
|
+
}
|
|
185
|
+
catch { /* stub may lack it */ }
|
|
186
|
+
// The host may load arbitrary URLs; route popups + cross-origin in-place
|
|
187
|
+
// navigation to the OS browser (mirror the native simulator hardening).
|
|
188
|
+
try {
|
|
189
|
+
view.webContents.setWindowOpenHandler(({ url }) => handleWindowOpenExternal(url));
|
|
190
|
+
}
|
|
191
|
+
catch { /* stub may lack it */ }
|
|
192
|
+
return view;
|
|
193
|
+
}
|
|
194
|
+
function setHostToolbarBounds(bounds) {
|
|
195
|
+
if (ctx.windows.mainWindow.isDestroyed())
|
|
196
|
+
return;
|
|
197
|
+
// Zero-area rect means "hide" — remove the child view but keep its
|
|
198
|
+
// WebContents (and the host's loaded content) alive. Do NOT create the
|
|
199
|
+
// view just to immediately hide it.
|
|
200
|
+
if (isHidden(bounds)) {
|
|
201
|
+
if (hostToolbarView && hostToolbarViewAdded && !hostToolbarView.webContents.isDestroyed()) {
|
|
202
|
+
try {
|
|
203
|
+
ctx.windows.mainWindow.contentView.removeChildView(hostToolbarView);
|
|
204
|
+
}
|
|
205
|
+
catch { /* already removed */ }
|
|
206
|
+
}
|
|
207
|
+
hostToolbarViewAdded = false;
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const view = ensureHostToolbarView();
|
|
211
|
+
if (!hostToolbarViewAdded) {
|
|
212
|
+
// addChildView appends = topmost z-order, which is correct for a strip
|
|
213
|
+
// that sits above the devtools header.
|
|
214
|
+
ctx.windows.mainWindow.contentView.addChildView(view);
|
|
215
|
+
hostToolbarViewAdded = true;
|
|
216
|
+
}
|
|
217
|
+
view.setBounds(bounds);
|
|
218
|
+
}
|
|
219
|
+
function setHostToolbarHeight(extent) {
|
|
220
|
+
// Push the reserved height back to the main-window renderer so its
|
|
221
|
+
// placeholder div resizes (closing the dynamic-height loop). The height is
|
|
222
|
+
// not retained in main — the renderer placeholder is the single source of
|
|
223
|
+
// truth, and the forward anchor re-reports bounds from it.
|
|
224
|
+
ctx.notify.hostToolbarHeightChanged(extent);
|
|
225
|
+
}
|
|
226
|
+
function hideHostToolbar() {
|
|
227
|
+
if (hostToolbarView && hostToolbarViewAdded && !ctx.windows.mainWindow.isDestroyed()) {
|
|
228
|
+
try {
|
|
229
|
+
ctx.windows.mainWindow.contentView.removeChildView(hostToolbarView);
|
|
230
|
+
}
|
|
231
|
+
catch { /* already removed */ }
|
|
232
|
+
}
|
|
233
|
+
hostToolbarViewAdded = false;
|
|
234
|
+
// Collapse the renderer placeholder to 0 too. Otherwise its anchor keeps a
|
|
235
|
+
// non-zero reserved height and re-publishes bounds on the next window
|
|
236
|
+
// resize, silently re-adding the view we just hid (unstable hide). Zeroing
|
|
237
|
+
// the height flips the anchor to `present:false` so it stops re-publishing.
|
|
238
|
+
ctx.notify.hostToolbarHeightChanged(0);
|
|
239
|
+
}
|
|
240
|
+
const hostToolbar = {
|
|
241
|
+
async loadURL(url) {
|
|
242
|
+
const view = ensureHostToolbarView();
|
|
243
|
+
await view.webContents.loadURL(url);
|
|
244
|
+
},
|
|
245
|
+
async loadFile(filePath) {
|
|
246
|
+
const view = ensureHostToolbarView();
|
|
247
|
+
await view.webContents.loadFile(filePath);
|
|
248
|
+
},
|
|
249
|
+
get webContents() {
|
|
250
|
+
if (!hostToolbarView)
|
|
251
|
+
return null;
|
|
252
|
+
if (hostToolbarView.webContents.isDestroyed())
|
|
253
|
+
return null;
|
|
254
|
+
return hostToolbarView.webContents;
|
|
255
|
+
},
|
|
256
|
+
hide() {
|
|
257
|
+
hideHostToolbar();
|
|
258
|
+
},
|
|
259
|
+
setPreloadPath(path) {
|
|
260
|
+
hostToolbarPreloadOverride = path;
|
|
261
|
+
},
|
|
262
|
+
};
|
|
49
263
|
function applySettingsBounds() {
|
|
50
264
|
if (!settingsView || ctx.windows.mainWindow.isDestroyed())
|
|
51
265
|
return;
|
|
@@ -58,6 +272,242 @@ export function createViewManager(ctx) {
|
|
|
58
272
|
const [w = 0, h = 0] = ctx.windows.mainWindow.getContentSize();
|
|
59
273
|
popoverView.setBounds(layout.computePopoverBounds(w, h, headerHeight));
|
|
60
274
|
}
|
|
275
|
+
function clearNativeDevtoolsRetry() {
|
|
276
|
+
nativeDevtoolsRetryToken++;
|
|
277
|
+
if (nativeDevtoolsRetryTimer) {
|
|
278
|
+
clearTimeout(nativeDevtoolsRetryTimer);
|
|
279
|
+
nativeDevtoolsRetryTimer = null;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function closeNativeDevtoolsSource() {
|
|
283
|
+
const source = nativeDevtoolsSourceWc;
|
|
284
|
+
nativeDevtoolsSourceWc = null;
|
|
285
|
+
if (!source || source.isDestroyed())
|
|
286
|
+
return;
|
|
287
|
+
try {
|
|
288
|
+
if (source.isDevToolsOpened()) {
|
|
289
|
+
source.closeDevTools();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
catch { /* source may be mid-destroy */ }
|
|
293
|
+
}
|
|
294
|
+
function stopFollowingNativeServiceHost() {
|
|
295
|
+
clearNativeDevtoolsRetry();
|
|
296
|
+
if (unsubscribeNativeRenderEvents) {
|
|
297
|
+
try {
|
|
298
|
+
unsubscribeNativeRenderEvents();
|
|
299
|
+
}
|
|
300
|
+
catch { /* ignore */ }
|
|
301
|
+
unsubscribeNativeRenderEvents = null;
|
|
302
|
+
}
|
|
303
|
+
closeNativeDevtoolsSource();
|
|
304
|
+
}
|
|
305
|
+
// ── Open-in-editor: click a console file link → built-in Monaco ──────────
|
|
306
|
+
// The right-panel console is the embedded Chromium DevTools front-end. Once a
|
|
307
|
+
// sourcemap maps a console frame back to source (restored by the service-host
|
|
308
|
+
// importScripts sourcemap rewrite), we redirect a source-link click to OUR
|
|
309
|
+
// Monaco editor instead of the DevTools Sources panel: install an "open
|
|
310
|
+
// resource handler" in the front-end realm that encodes (url, line, col) into
|
|
311
|
+
// a sentinel URL and asks the front-end to open it; Electron surfaces that as
|
|
312
|
+
// `devtools-open-url` on the inspected (service-host) wc, which we decode,
|
|
313
|
+
// map to a project-relative path, and broadcast to the renderer's Monaco.
|
|
314
|
+
const openInEditorWiredWcIds = new Set();
|
|
315
|
+
function injectOpenResourceHandler(devtoolsWc) {
|
|
316
|
+
// `setOpenResourceHandler` is the official Chromium DevTools hook IDEs use
|
|
317
|
+
// to route source-link clicks to an external editor. We poll for the host
|
|
318
|
+
// (it appears once the front-end finishes bootstrapping, like `UI` above)
|
|
319
|
+
// and register a handler that re-emits an encoded sentinel via
|
|
320
|
+
// `openInNewTab` → Electron `devtools-open-url`. Best-effort: wrapped in
|
|
321
|
+
// try/catch and a bounded poll so a missing API never throws.
|
|
322
|
+
const scheme = JSON.stringify(OPEN_IN_EDITOR_SCHEME);
|
|
323
|
+
devtoolsWc.executeJavaScript(`
|
|
324
|
+
(function() {
|
|
325
|
+
try {
|
|
326
|
+
let tries = 0
|
|
327
|
+
const timer = setInterval(() => {
|
|
328
|
+
tries++
|
|
329
|
+
try {
|
|
330
|
+
const Host = globalThis.Host
|
|
331
|
+
const host = Host && Host.InspectorFrontendHost
|
|
332
|
+
if (host && typeof host.setOpenResourceHandler === 'function'
|
|
333
|
+
&& typeof host.openInNewTab === 'function') {
|
|
334
|
+
host.setOpenResourceHandler((url, lineNumber, columnNumber) => {
|
|
335
|
+
try {
|
|
336
|
+
const p = new URLSearchParams()
|
|
337
|
+
p.set('u', String(url))
|
|
338
|
+
if (typeof lineNumber === 'number') p.set('l', String(lineNumber))
|
|
339
|
+
if (typeof columnNumber === 'number') p.set('c', String(columnNumber))
|
|
340
|
+
host.openInNewTab(${scheme} + ':?' + p.toString())
|
|
341
|
+
} catch (_) {}
|
|
342
|
+
})
|
|
343
|
+
clearInterval(timer)
|
|
344
|
+
return
|
|
345
|
+
}
|
|
346
|
+
} catch (_) {}
|
|
347
|
+
if (tries > 80) clearInterval(timer)
|
|
348
|
+
}, 50)
|
|
349
|
+
} catch (_) {}
|
|
350
|
+
})()
|
|
351
|
+
`).catch(() => { });
|
|
352
|
+
}
|
|
353
|
+
// ── DevTools tab customization: keep only Elements/Console/Network ──────────
|
|
354
|
+
// Inject into the same DevTools front-end host wc that the console/network
|
|
355
|
+
// injectors target. Reorders Elements/Console/Network to the front and closes
|
|
356
|
+
// every other panel tab by driving the front-end's UI.ViewManager /
|
|
357
|
+
// InspectorView.tabbedLocation (see ./devtools-tabs.ts). Best-effort: the
|
|
358
|
+
// injected script bounded-polls for the lazily-registered panels, wraps
|
|
359
|
+
// everything in try/catch, and silently no-ops if the API never appears —
|
|
360
|
+
// DevTools default tab behaviour is preserved on any failure. Re-injected on
|
|
361
|
+
// every (re)point so a re-`openDevTools` (service-host pool swap) re-applies.
|
|
362
|
+
function customizeDevtoolsTabs(devtoolsWc) {
|
|
363
|
+
try {
|
|
364
|
+
if (devtoolsWc.isDestroyed())
|
|
365
|
+
return;
|
|
366
|
+
const inject = () => {
|
|
367
|
+
if (devtoolsWc.isDestroyed())
|
|
368
|
+
return;
|
|
369
|
+
try {
|
|
370
|
+
void devtoolsWc.executeJavaScript(buildCustomizeTabsScript()).catch(() => { });
|
|
371
|
+
}
|
|
372
|
+
catch { /* wc torn down mid-call */ }
|
|
373
|
+
};
|
|
374
|
+
if (devtoolsWc.isLoading()) {
|
|
375
|
+
devtoolsWc.once('dom-ready', inject);
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
inject();
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
catch { /* wc surface incomplete / torn down; degrade silently */ }
|
|
382
|
+
}
|
|
383
|
+
function wireOpenInEditor(serviceWc, devtoolsWc) {
|
|
384
|
+
// Inject the front-end handler on every (re)attach — the DevTools front-end
|
|
385
|
+
// wc is recreated per simulatorView, so a fresh host needs the handler set.
|
|
386
|
+
if (!devtoolsWc.isDestroyed()) {
|
|
387
|
+
if (devtoolsWc.isLoading()) {
|
|
388
|
+
devtoolsWc.once('dom-ready', () => injectOpenResourceHandler(devtoolsWc));
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
injectOpenResourceHandler(devtoolsWc);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
// Attach the `devtools-open-url` decoder to the inspected service wc once
|
|
395
|
+
// (the listener is keyed off the encoded sentinel scheme, so it only acts on
|
|
396
|
+
// OUR redirected links; any other devtools "open in new tab" falls through).
|
|
397
|
+
if (openInEditorWiredWcIds.has(serviceWc.id))
|
|
398
|
+
return;
|
|
399
|
+
openInEditorWiredWcIds.add(serviceWc.id);
|
|
400
|
+
const onOpenUrl = (_event, url) => {
|
|
401
|
+
const req = decodeOpenInEditorUrl(url);
|
|
402
|
+
if (!req)
|
|
403
|
+
return; // not our sentinel — leave it to Electron's default path
|
|
404
|
+
const rel = resourceUrlToProjectRelativePath(req.url);
|
|
405
|
+
if (!rel)
|
|
406
|
+
return;
|
|
407
|
+
// DevTools reports 0-based line/column; Monaco is 1-based.
|
|
408
|
+
const line = typeof req.line === 'number' ? req.line + 1 : undefined;
|
|
409
|
+
const column = typeof req.column === 'number' ? req.column + 1 : undefined;
|
|
410
|
+
ctx.notify.editorOpenFile({ path: rel, line, column });
|
|
411
|
+
};
|
|
412
|
+
serviceWc.on('devtools-open-url', onOpenUrl);
|
|
413
|
+
// Consolidate teardown onto the connection layer (foundation.md §4 / P2),
|
|
414
|
+
// but as a wc-LIFETIME resource: the open-in-editor wiring inspects this
|
|
415
|
+
// service-host wc and must SURVIVE pool reuse (`reset`) — the dedup set
|
|
416
|
+
// entry + listener stay valid across sessions reusing the same wc, and the
|
|
417
|
+
// early-return dedup above correctly skips re-wiring after a reset. So we
|
|
418
|
+
// register on `'closed'` (fires only on real wc destroy), NOT `own()`
|
|
419
|
+
// (which also fires on `reset` and would leave the wc un-wired after reuse
|
|
420
|
+
// because re-pointing the same wc.id early-returns). acquire() is
|
|
421
|
+
// idempotent — bridge-router owning session-scoped resources on the same
|
|
422
|
+
// serviceWc connection coexists cleanly.
|
|
423
|
+
const conn = ctx.connections.acquire(serviceWc);
|
|
424
|
+
conn.on('closed', () => {
|
|
425
|
+
openInEditorWiredWcIds.delete(serviceWc.id);
|
|
426
|
+
try {
|
|
427
|
+
serviceWc.removeListener('devtools-open-url', onOpenUrl);
|
|
428
|
+
}
|
|
429
|
+
catch { /* wc gone */ }
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
// Point the right-panel Chrome DevTools front-end at `next` — the SERVICE HOST
|
|
433
|
+
// webContents (logic layer). Idempotent: if we already inspect this wc, no-op.
|
|
434
|
+
function pointNativeDevtoolsAtServiceWc(next) {
|
|
435
|
+
if (!simulatorView || simulatorView.webContents.isDestroyed())
|
|
436
|
+
return true;
|
|
437
|
+
if (nativeDevtoolsSourceWc?.id === next.id && !nativeDevtoolsSourceWc.isDestroyed()) {
|
|
438
|
+
return true;
|
|
439
|
+
}
|
|
440
|
+
closeNativeDevtoolsSource();
|
|
441
|
+
nativeDevtoolsSourceWc = next;
|
|
442
|
+
try {
|
|
443
|
+
next.setDevToolsWebContents(simulatorView.webContents);
|
|
444
|
+
// DevTools renders into the right-panel host view (simulatorView); with a
|
|
445
|
+
// custom host the `mode` is overridden, and `activate:false` prevents it
|
|
446
|
+
// stealing focus — this re-points whenever the service window is swapped,
|
|
447
|
+
// so a focusing window would yank focus repeatedly (disrupting the user /
|
|
448
|
+
// e2e).
|
|
449
|
+
next.openDevTools({ mode: 'detach', activate: false });
|
|
450
|
+
// Redirect console source-link clicks to the built-in Monaco editor.
|
|
451
|
+
wireOpenInEditor(next, simulatorView.webContents);
|
|
452
|
+
// Keep only Elements/Console/Network tabs (front-most, in that order).
|
|
453
|
+
// Re-applied on every re-point so a service-host pool swap (fresh
|
|
454
|
+
// openDevTools) re-asserts the custom tab bar.
|
|
455
|
+
customizeDevtoolsTabs(simulatorView.webContents);
|
|
456
|
+
return true;
|
|
457
|
+
}
|
|
458
|
+
catch {
|
|
459
|
+
if (nativeDevtoolsSourceWc?.id === next.id) {
|
|
460
|
+
nativeDevtoolsSourceWc = null;
|
|
461
|
+
}
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
// Resolve the SERVICE HOST webContents for the active (or named) app. This is
|
|
466
|
+
// the hidden service BrowserWindow's wc — a top-level wc that CAN host a
|
|
467
|
+
// Chrome DevTools front-end (unlike a `<webview>` guest's). Re-resolved fresh
|
|
468
|
+
// so a pre-warm-pool swap on respawn is tolerated.
|
|
469
|
+
function pointNativeDevtoolsAtActiveServiceHost(appId) {
|
|
470
|
+
if (!ctx.bridge?.isNativeHost())
|
|
471
|
+
return true;
|
|
472
|
+
if (!simulatorView || simulatorView.webContents.isDestroyed())
|
|
473
|
+
return true;
|
|
474
|
+
const wc = ctx.bridge.getServiceWc(appId);
|
|
475
|
+
if (!wc || wc.isDestroyed())
|
|
476
|
+
return false;
|
|
477
|
+
return pointNativeDevtoolsAtServiceWc(wc);
|
|
478
|
+
}
|
|
479
|
+
function scheduleNativeDevtoolsFollow(appId, attempt = 0) {
|
|
480
|
+
if (attempt >= 20)
|
|
481
|
+
return;
|
|
482
|
+
if (!ctx.bridge?.isNativeHost())
|
|
483
|
+
return;
|
|
484
|
+
const token = nativeDevtoolsRetryToken;
|
|
485
|
+
if (nativeDevtoolsRetryTimer)
|
|
486
|
+
clearTimeout(nativeDevtoolsRetryTimer);
|
|
487
|
+
nativeDevtoolsRetryTimer = setTimeout(() => {
|
|
488
|
+
nativeDevtoolsRetryTimer = null;
|
|
489
|
+
if (token !== nativeDevtoolsRetryToken)
|
|
490
|
+
return;
|
|
491
|
+
if (pointNativeDevtoolsAtActiveServiceHost(appId))
|
|
492
|
+
return;
|
|
493
|
+
scheduleNativeDevtoolsFollow(appId, attempt + 1);
|
|
494
|
+
}, 50);
|
|
495
|
+
}
|
|
496
|
+
function followNativeDevtoolsServiceHost(appId) {
|
|
497
|
+
clearNativeDevtoolsRetry();
|
|
498
|
+
if (pointNativeDevtoolsAtActiveServiceHost(appId))
|
|
499
|
+
return;
|
|
500
|
+
scheduleNativeDevtoolsFollow(appId);
|
|
501
|
+
}
|
|
502
|
+
// Render-side activity (a page DOM mounting / the visible page changing)
|
|
503
|
+
// always follows a spawn or respawn, by which point the service window exists
|
|
504
|
+
// (and may have been swapped by the pool). Re-resolve + re-point the DevTools
|
|
505
|
+
// at the now-current service host on every such event.
|
|
506
|
+
function onNativeRenderEvent(event) {
|
|
507
|
+
if (event.kind !== 'activePage' && event.kind !== 'domReady')
|
|
508
|
+
return;
|
|
509
|
+
followNativeDevtoolsServiceHost(event.appId);
|
|
510
|
+
}
|
|
61
511
|
// ── ViewManager methods ─────────────────────────────────────────────────
|
|
62
512
|
function attachSimulator(simWcId, simWidth) {
|
|
63
513
|
const sim = webContents.fromId(simWcId);
|
|
@@ -80,12 +530,20 @@ export function createViewManager(ctx) {
|
|
|
80
530
|
}
|
|
81
531
|
simulatorView = new WebContentsView();
|
|
82
532
|
sim.setDevToolsWebContents(simulatorView.webContents);
|
|
83
|
-
|
|
533
|
+
// Embedded in the right-panel host view; never bring it to the foreground
|
|
534
|
+
// (would steal focus from the user / disrupt e2e).
|
|
535
|
+
sim.openDevTools({ mode: 'detach', activate: false });
|
|
84
536
|
// Default DevTools to Console panel (Chrome DevTools defaults to Elements).
|
|
85
|
-
// The DevTools UI
|
|
86
|
-
//
|
|
87
|
-
//
|
|
537
|
+
// The DevTools UI lives inside closed shadow roots, so a light-DOM
|
|
538
|
+
// querySelector('[role="tab"]') cannot reach the tab bar to click it.
|
|
539
|
+
// Instead drive the front-end's own view manager: the bundled DevTools
|
|
540
|
+
// exposes `UI.ViewManager.instance().showView(id)` on `globalThis.UI`
|
|
541
|
+
// once the front-end has finished bootstrapping. We poll for it and
|
|
542
|
+
// request the `console` view, and also persist the choice via the
|
|
543
|
+
// `panel-selectedTab` localStorage key so subsequent reloads honor it.
|
|
88
544
|
const devtoolsWc = simulatorView.webContents;
|
|
545
|
+
// Keep only Elements/Console/Network tabs (front-most, in that order).
|
|
546
|
+
customizeDevtoolsTabs(devtoolsWc);
|
|
89
547
|
devtoolsWc.once('dom-ready', () => {
|
|
90
548
|
devtoolsWc.executeJavaScript(`
|
|
91
549
|
(function() {
|
|
@@ -93,27 +551,380 @@ export function createViewManager(ctx) {
|
|
|
93
551
|
let tries = 0
|
|
94
552
|
const timer = setInterval(() => {
|
|
95
553
|
tries++
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
554
|
+
try {
|
|
555
|
+
const UI = globalThis.UI
|
|
556
|
+
const vm = UI && UI.ViewManager && typeof UI.ViewManager.instance === 'function'
|
|
557
|
+
? UI.ViewManager.instance()
|
|
558
|
+
: null
|
|
559
|
+
if (vm && typeof vm.showView === 'function') {
|
|
560
|
+
vm.showView('console')
|
|
101
561
|
clearInterval(timer)
|
|
102
562
|
return
|
|
103
563
|
}
|
|
564
|
+
} catch {}
|
|
565
|
+
if (tries > 80) clearInterval(timer)
|
|
566
|
+
}, 50)
|
|
567
|
+
})()
|
|
568
|
+
`).catch(() => { });
|
|
569
|
+
});
|
|
570
|
+
if (getDefaultTab(ctx) === 'simulator') {
|
|
571
|
+
if (simulatorBoundsOverride) {
|
|
572
|
+
if (!isHidden(simulatorBoundsOverride)) {
|
|
573
|
+
ctx.windows.mainWindow.contentView.addChildView(simulatorView);
|
|
574
|
+
simulatorViewAdded = true;
|
|
575
|
+
simulatorView.setBounds(simulatorBoundsOverride);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
else {
|
|
579
|
+
ctx.windows.mainWindow.contentView.addChildView(simulatorView);
|
|
580
|
+
simulatorViewAdded = true;
|
|
581
|
+
applySimulatorBounds(simWidth);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
function attachNativeSimulatorDevtoolsHost(simWidth) {
|
|
586
|
+
stopFollowingNativeServiceHost();
|
|
587
|
+
// Destroy old simulatorView to prevent WebContentsView leak
|
|
588
|
+
if (simulatorView) {
|
|
589
|
+
hideSimulator();
|
|
590
|
+
try {
|
|
591
|
+
if (!simulatorView.webContents.isDestroyed()) {
|
|
592
|
+
simulatorView.webContents.close();
|
|
593
|
+
}
|
|
104
594
|
}
|
|
105
|
-
|
|
106
|
-
|
|
595
|
+
catch { /* ignore */ }
|
|
596
|
+
simulatorView = null;
|
|
597
|
+
}
|
|
598
|
+
simulatorView = new WebContentsView();
|
|
599
|
+
// Default DevTools to Console panel (Chrome DevTools defaults to Elements).
|
|
600
|
+
// The DevTools UI lives inside closed shadow roots, so a light-DOM
|
|
601
|
+
// querySelector('[role="tab"]') cannot reach the tab bar to click it.
|
|
602
|
+
// Instead drive the front-end's own view manager: the bundled DevTools
|
|
603
|
+
// exposes `UI.ViewManager.instance().showView(id)` on `globalThis.UI`
|
|
604
|
+
// once the front-end has finished bootstrapping. We poll for it and
|
|
605
|
+
// request the `console` view, and also persist the choice via the
|
|
606
|
+
// `panel-selectedTab` localStorage key so subsequent reloads honor it.
|
|
607
|
+
const devtoolsWc = simulatorView.webContents;
|
|
608
|
+
// Hand the network forwarder THIS front-end host wc — it injects the
|
|
609
|
+
// simulator WCV's Network.* CDP events into `window.DevToolsAPI.dispatchMessage`
|
|
610
|
+
// here so the native Network tab renders them (falls back to the service-host
|
|
611
|
+
// console line when null / the API never appears). Cleared on detach.
|
|
612
|
+
ctx.networkForward?.setDevtoolsHost(devtoolsWc);
|
|
613
|
+
// Elements forwarding (production, always on for the native simulator): route
|
|
614
|
+
// the front-end's Elements-panel CDP traffic (DOM/CSS/Overlay/DOMSnapshot/
|
|
615
|
+
// DOMDebugger) onto the ACTIVE RENDER GUEST so the panel reflects the page's
|
|
616
|
+
// live DOM tree instead of the service host it natively inspects. Reuses the
|
|
617
|
+
// safe-area-attached debugger session (never detaches one it doesn't own);
|
|
618
|
+
// Emulation.* and every other domain stay on the service-host path. Degrades
|
|
619
|
+
// to the native service-host DOM if the front-end hook is unavailable. The
|
|
620
|
+
// disposer is stopped on detach / host destroyed.
|
|
621
|
+
if (ctx.bridge) {
|
|
622
|
+
try {
|
|
623
|
+
stopElementsForward?.();
|
|
624
|
+
}
|
|
625
|
+
catch { /* prior disposer already gone */ }
|
|
626
|
+
stopElementsForward = installElementsForward({ devtoolsWc, bridge: ctx.bridge, connections: ctx.connections });
|
|
627
|
+
devtoolsWc.once('destroyed', () => {
|
|
628
|
+
try {
|
|
629
|
+
stopElementsForward?.();
|
|
630
|
+
}
|
|
631
|
+
catch { /* already stopped */ }
|
|
632
|
+
stopElementsForward = null;
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
devtoolsWc.once('dom-ready', () => {
|
|
636
|
+
devtoolsWc.executeJavaScript(`
|
|
637
|
+
(function() {
|
|
638
|
+
try { localStorage.setItem('panel-selectedTab', '"console"') } catch {}
|
|
639
|
+
let tries = 0
|
|
640
|
+
const timer = setInterval(() => {
|
|
641
|
+
tries++
|
|
642
|
+
try {
|
|
643
|
+
const UI = globalThis.UI
|
|
644
|
+
const vm = UI && UI.ViewManager && typeof UI.ViewManager.instance === 'function'
|
|
645
|
+
? UI.ViewManager.instance()
|
|
646
|
+
: null
|
|
647
|
+
if (vm && typeof vm.showView === 'function') {
|
|
648
|
+
vm.showView('console')
|
|
649
|
+
clearInterval(timer)
|
|
650
|
+
return
|
|
651
|
+
}
|
|
652
|
+
} catch {}
|
|
653
|
+
if (tries > 80) clearInterval(timer)
|
|
654
|
+
}, 50)
|
|
107
655
|
})()
|
|
108
656
|
`).catch(() => { });
|
|
109
657
|
});
|
|
110
658
|
if (getDefaultTab(ctx) === 'simulator') {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
659
|
+
if (simulatorBoundsOverride) {
|
|
660
|
+
if (!isHidden(simulatorBoundsOverride)) {
|
|
661
|
+
ctx.windows.mainWindow.contentView.addChildView(simulatorView);
|
|
662
|
+
simulatorViewAdded = true;
|
|
663
|
+
simulatorView.setBounds(simulatorBoundsOverride);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
ctx.windows.mainWindow.contentView.addChildView(simulatorView);
|
|
668
|
+
simulatorViewAdded = true;
|
|
669
|
+
applySimulatorBounds(simWidth);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
if (ctx.bridge?.isNativeHost()) {
|
|
673
|
+
unsubscribeNativeRenderEvents = ctx.bridge.onRenderEvent(onNativeRenderEvent);
|
|
674
|
+
followNativeDevtoolsServiceHost();
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Remove the custom-apis bridge `ipcMain.on` listener, if any. Idempotent.
|
|
679
|
+
*/
|
|
680
|
+
function detachNativeCustomApiBridge() {
|
|
681
|
+
if (nativeCustomApiBridgeHandler) {
|
|
682
|
+
ipcMain.removeListener(SimulatorCustomApiBridgeChannel.Request, nativeCustomApiBridgeHandler);
|
|
683
|
+
nativeCustomApiBridgeHandler = null;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* NATIVE-HOST ONLY. The native simulator is a top-level WebContentsView with
|
|
688
|
+
* NO embedder renderer, so the simulator-side `__diminaCustomApis` bridge
|
|
689
|
+
* (`src/preload/runtime/custom-apis.ts`) cannot reach the host via
|
|
690
|
+
* `ipcRenderer.sendToHost` — that only delivers to a `<webview>`'s embedder,
|
|
691
|
+
* which is how the default path's `useCustomApiProxy` answered it. A
|
|
692
|
+
* top-level WebContentsView has no embedder, and `sendToHost` does NOT loop
|
|
693
|
+
* back as `ipc-message-host` on itself, so that channel never fires here.
|
|
694
|
+
*
|
|
695
|
+
* Under native-host the bridge instead talks to `ipcMain` directly (the same
|
|
696
|
+
* way `installNativeHostBridge` issues SPAWN/PAGE_OPEN): the preload sends
|
|
697
|
+
* `SimulatorCustomApiBridgeChannel.Request` via `ipcRenderer.send`, this
|
|
698
|
+
* `ipcMain.on` listener answers it. We do NOT route through `IpcRegistry` /
|
|
699
|
+
* the sender-policy white-list (the simulator is deliberately kept off it);
|
|
700
|
+
* instead we accept the message ONLY when `event.sender` is THIS precise
|
|
701
|
+
* simWc — the same trust model bridge-router uses for the simulator's own
|
|
702
|
+
* SPAWN messages. The result is dispatched through the shared
|
|
703
|
+
* `ctx.simulatorApis` registry and the id-correlated `Response` is posted
|
|
704
|
+
* back via `simWc.send`, which the preload's `ipcRenderer.on(Response)`
|
|
705
|
+
* listener settles.
|
|
706
|
+
*/
|
|
707
|
+
function attachNativeCustomApiBridge(simWc) {
|
|
708
|
+
// Re-attach defensively: a stale listener from a prior simWc must never
|
|
709
|
+
// linger. (attachNativeSimulator already tears the old view down first.)
|
|
710
|
+
detachNativeCustomApiBridge();
|
|
711
|
+
const apis = ctx.simulatorApis;
|
|
712
|
+
if (!apis)
|
|
713
|
+
return;
|
|
714
|
+
const simWcId = simWc.id;
|
|
715
|
+
const handler = (event, req) => {
|
|
716
|
+
// Trust gate: only the exact native simulator webContents may drive this.
|
|
717
|
+
if (event.sender.id !== simWcId)
|
|
718
|
+
return;
|
|
719
|
+
const r = req;
|
|
720
|
+
if (!r || typeof r.id !== 'number')
|
|
721
|
+
return;
|
|
722
|
+
void handleCustomApiBridgeRequest(apis, r).then((response) => {
|
|
723
|
+
if (simWc.isDestroyed())
|
|
724
|
+
return;
|
|
725
|
+
simWc.send(SimulatorCustomApiBridgeChannel.Response, response);
|
|
726
|
+
}).catch(() => { });
|
|
727
|
+
};
|
|
728
|
+
nativeCustomApiBridgeHandler = handler;
|
|
729
|
+
ipcMain.on(SimulatorCustomApiBridgeChannel.Request, handler);
|
|
730
|
+
// Consolidate this per-webContents teardown onto the connection layer
|
|
731
|
+
// (foundation.md §4 / P1 DoD #3): acquiring the simWc connection makes the
|
|
732
|
+
// registry track this trusted webContents, and `own()`-ing the detach ties
|
|
733
|
+
// the ipcMain listener's lifetime to the connection so it's torn down
|
|
734
|
+
// deterministically when the wc is destroyed (or the connection is reset),
|
|
735
|
+
// instead of a bespoke `once('destroyed')`. The detach stays idempotent, so
|
|
736
|
+
// the defensive re-attach path calling it directly remains safe.
|
|
737
|
+
ctx.connections.acquire(simWc).own(detachNativeCustomApiBridge);
|
|
738
|
+
}
|
|
739
|
+
function attachNativeSimulator(simulatorUrl, simWidth) {
|
|
740
|
+
if (!ctx.preloadPath) {
|
|
741
|
+
console.error('[workbench] attachNativeSimulator — preloadPath unset; cannot mount native simulator');
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
lastSimWidth = simWidth;
|
|
745
|
+
// Tear down any previous native simulator view (relaunch / re-open).
|
|
746
|
+
if (nativeSimulatorView) {
|
|
747
|
+
detachNativeCustomApiBridge();
|
|
748
|
+
if (nativeSimulatorViewAdded) {
|
|
749
|
+
try {
|
|
750
|
+
ctx.windows.mainWindow.contentView.removeChildView(nativeSimulatorView);
|
|
751
|
+
}
|
|
752
|
+
catch { /* already removed */ }
|
|
753
|
+
nativeSimulatorViewAdded = false;
|
|
754
|
+
}
|
|
755
|
+
try {
|
|
756
|
+
if (!nativeSimulatorView.webContents.isDestroyed()) {
|
|
757
|
+
nativeSimulatorView.webContents.close();
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
catch { /* ignore */ }
|
|
761
|
+
nativeSimulatorView = null;
|
|
114
762
|
}
|
|
763
|
+
// The simulator preload is a CJS bundle; webPreferences.preload obeys the
|
|
764
|
+
// `.js` + "type":"module" ESM rule (require would be undefined), so hand the
|
|
765
|
+
// top-level WebContentsView the `.cjs` sibling. contextIsolation:false +
|
|
766
|
+
// sandbox:false + webviewTag:true mirror what the default `<webview>` guest
|
|
767
|
+
// runs with, and `partition:'persist:simulator'` shares storage + the
|
|
768
|
+
// session-registered preload/CORS rules with the rest of the simulator.
|
|
769
|
+
const view = new WebContentsView({
|
|
770
|
+
webPreferences: {
|
|
771
|
+
nodeIntegration: false,
|
|
772
|
+
contextIsolation: false,
|
|
773
|
+
sandbox: false,
|
|
774
|
+
webviewTag: true,
|
|
775
|
+
preload: cjsSiblingPreloadPath(ctx.preloadPath),
|
|
776
|
+
partition: 'persist:simulator',
|
|
777
|
+
},
|
|
778
|
+
});
|
|
779
|
+
nativeSimulatorView = view;
|
|
780
|
+
// Paint the WCV surface the simulator-panel background (≈ --color-sim-bg
|
|
781
|
+
// hsl(0 0% 7%)) so a height-resize that grows the region never flashes white
|
|
782
|
+
// in the newly-exposed strip before DeviceShell's desk repaints — the WCV,
|
|
783
|
+
// the desk, and the renderer placeholder behind it are all the same color.
|
|
784
|
+
view.setBackgroundColor('#121212');
|
|
785
|
+
const simWc = view.webContents;
|
|
786
|
+
// Service the simulator-side `__diminaCustomApis` bridge: this top-level
|
|
787
|
+
// WebContentsView has no embedder renderer to proxy through, so dispatch its
|
|
788
|
+
// `sendToHost` requests straight to `ctx.simulatorApis` from main.
|
|
789
|
+
attachNativeCustomApiBridge(simWc);
|
|
790
|
+
// DeviceShell mounts per-page render-host `<webview>`s INSIDE this view.
|
|
791
|
+
// Mirror windows/main-window/create.ts: pin them onto persist:simulator and
|
|
792
|
+
// run them with contextIsolation/sandbox off so the render runtime + its
|
|
793
|
+
// preload share the page realm. (A top-level WebContentsView can host these
|
|
794
|
+
// guests; a `<webview>` guest cannot — that's the whole point of Option A.)
|
|
795
|
+
simWc.on('will-attach-webview', (_event, webPreferences, params) => {
|
|
796
|
+
;
|
|
797
|
+
webPreferences.partition = 'persist:simulator';
|
|
798
|
+
params.partition = 'persist:simulator';
|
|
799
|
+
webPreferences.contextIsolation = false;
|
|
800
|
+
webPreferences.sandbox = false;
|
|
801
|
+
});
|
|
802
|
+
simWc.on('did-attach-webview', (_event, guestWc) => {
|
|
803
|
+
// Scale the nested render-host page with the device zoom. The host WCV is
|
|
804
|
+
// sized to the SCALED bezel rect and runs at `currentZoomFactor`, so the
|
|
805
|
+
// guest must run at the same factor to lay the page out at the logical
|
|
806
|
+
// device width and paint at the right scale. At 100% (default) this is a
|
|
807
|
+
// no-op identity (factor 1). Newly-attached guests pick up the latest
|
|
808
|
+
// zoom here; live zoom changes re-apply via setNativeSimulatorViewBounds.
|
|
809
|
+
try {
|
|
810
|
+
guestWc.setZoomFactor(currentZoomFactor);
|
|
811
|
+
}
|
|
812
|
+
catch { /* guest not ready; setNativeSimulatorViewBounds re-applies */ }
|
|
813
|
+
// Simulate this device's CSS env(safe-area-inset-*) on the fresh guest
|
|
814
|
+
// before it paints, so notch-aware page layout resolves correctly.
|
|
815
|
+
safeArea.applyToGuest(guestWc, ctx.bridge?.getDevice() ?? null);
|
|
816
|
+
guestWc.setWindowOpenHandler(({ url }) => handleWindowOpenExternal(url));
|
|
817
|
+
guestWc.on('will-navigate', (e, url) => {
|
|
818
|
+
try {
|
|
819
|
+
const u = new URL(url);
|
|
820
|
+
if (u.protocol === 'about:')
|
|
821
|
+
return;
|
|
822
|
+
if ((u.protocol === 'http:' || u.protocol === 'https:')
|
|
823
|
+
&& (u.hostname === 'localhost' || u.hostname === '127.0.0.1')) {
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
if (u.protocol === 'file:')
|
|
827
|
+
return;
|
|
828
|
+
e.preventDefault();
|
|
829
|
+
if (u.protocol === 'http:' || u.protocol === 'https:') {
|
|
830
|
+
void shell.openExternal(url);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
catch {
|
|
834
|
+
e.preventDefault();
|
|
835
|
+
}
|
|
836
|
+
});
|
|
837
|
+
// A render-host guest only attaches after a spawn, so the service window
|
|
838
|
+
// exists by now — (re)point the right-panel DevTools at it. Belt-and-braces
|
|
839
|
+
// with the `onRenderEvent` path in case its emit lost the attach race.
|
|
840
|
+
followNativeDevtoolsServiceHost();
|
|
841
|
+
});
|
|
842
|
+
// The simulator loads http://localhost:<port>/simulator.html. Harden popups
|
|
843
|
+
// + in-place navigation: allow the dev server origin (and about:blank /
|
|
844
|
+
// file:// render hosts), route everything else to the OS browser.
|
|
845
|
+
simWc.setWindowOpenHandler(({ url }) => handleWindowOpenExternal(url));
|
|
846
|
+
simWc.on('will-navigate', (e, url) => {
|
|
847
|
+
try {
|
|
848
|
+
const u = new URL(url);
|
|
849
|
+
if (u.protocol === 'about:')
|
|
850
|
+
return;
|
|
851
|
+
if (u.protocol === 'file:')
|
|
852
|
+
return;
|
|
853
|
+
if ((u.protocol === 'http:' || u.protocol === 'https:')
|
|
854
|
+
&& (u.hostname === 'localhost' || u.hostname === '127.0.0.1')) {
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
e.preventDefault();
|
|
858
|
+
if (u.protocol === 'http:' || u.protocol === 'https:') {
|
|
859
|
+
void shell.openExternal(url);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
catch {
|
|
863
|
+
e.preventDefault();
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
void simWc.loadURL(simulatorUrl).catch((err) => {
|
|
867
|
+
console.error('[workbench] attachNativeSimulator — loadURL failed', err);
|
|
868
|
+
});
|
|
869
|
+
// This WebContentsView's webContents IS the simulator: the native-host
|
|
870
|
+
// preload runs here and issues SPAWN, so bridge-router resolves
|
|
871
|
+
// ap.simulatorWc = event.sender = this wc, and SIMULATOR_EVENTS flow back.
|
|
872
|
+
simulatorWebContentsId = simWc.id;
|
|
873
|
+
// Forward this WCV's network requests (wx.request/download/upload run here,
|
|
874
|
+
// not in the service host) into the service-host console so they show in the
|
|
875
|
+
// embedded DevTools the right panel hosts. Attaches the CDP debugger; no-op
|
|
876
|
+
// if the forwarder is unwired (partial test ctx) or the debugger is claimed.
|
|
877
|
+
ctx.networkForward?.attachSimulator(simWc);
|
|
878
|
+
// Model A: bounds come ONLY from the renderer's measured inner-screen rect.
|
|
879
|
+
// If a report already landed before this attach (project-open ordering),
|
|
880
|
+
// replay it now — setNativeSimulatorViewBounds adds + sizes the view. Else
|
|
881
|
+
// the view stays unadded until the next reportBounds. No coarse fallback.
|
|
882
|
+
if (lastRendererRect) {
|
|
883
|
+
setNativeSimulatorViewBounds(lastRendererRect);
|
|
884
|
+
}
|
|
885
|
+
// Native-host page code (console.log / wx.request / Sources) runs in the
|
|
886
|
+
// hidden SERVICE HOST window, not this DeviceShell host nor the render-host
|
|
887
|
+
// guests. Keep the right-panel DevTools host view stable and point it at the
|
|
888
|
+
// service host so its Console/Network(fetch)/Sources reflect the logic layer.
|
|
889
|
+
// (The UI/view layer's Elements equivalent is the native WXML panel +
|
|
890
|
+
// render-guest highlight chain, which targets the active render guest.)
|
|
891
|
+
attachNativeSimulatorDevtoolsHost(simWidth);
|
|
115
892
|
}
|
|
116
893
|
function detachSimulator() {
|
|
894
|
+
stopFollowingNativeServiceHost();
|
|
895
|
+
detachNativeCustomApiBridge();
|
|
896
|
+
// Stop Elements forwarding (detaches only the debugger sessions IT attached;
|
|
897
|
+
// safe-area's sessions are untouched). Idempotent — the host 'destroyed'
|
|
898
|
+
// handler may have already run.
|
|
899
|
+
try {
|
|
900
|
+
stopElementsForward?.();
|
|
901
|
+
}
|
|
902
|
+
catch { /* already stopped */ }
|
|
903
|
+
stopElementsForward = null;
|
|
904
|
+
// Stop forwarding the simulator WCV's network (its debugger is detached as
|
|
905
|
+
// the view is closed below, but drop our session deterministically first).
|
|
906
|
+
// Also drop the DevTools front-end host — its wc is destroyed below; a stale
|
|
907
|
+
// ref would make the forwarder dispatch into a dead wc instead of falling
|
|
908
|
+
// back to the console.
|
|
909
|
+
ctx.networkForward?.detachSimulator();
|
|
910
|
+
ctx.networkForward?.setDevtoolsHost(null);
|
|
911
|
+
// Native-host simulator content view (no-op in the default path).
|
|
912
|
+
if (nativeSimulatorView) {
|
|
913
|
+
if (nativeSimulatorViewAdded && !ctx.windows.mainWindow.isDestroyed()) {
|
|
914
|
+
try {
|
|
915
|
+
ctx.windows.mainWindow.contentView.removeChildView(nativeSimulatorView);
|
|
916
|
+
}
|
|
917
|
+
catch { /* already removed */ }
|
|
918
|
+
}
|
|
919
|
+
try {
|
|
920
|
+
if (!nativeSimulatorView.webContents.isDestroyed()) {
|
|
921
|
+
nativeSimulatorView.webContents.close();
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
catch { /* ignore */ }
|
|
925
|
+
nativeSimulatorView = null;
|
|
926
|
+
nativeSimulatorViewAdded = false;
|
|
927
|
+
}
|
|
117
928
|
hidePopover();
|
|
118
929
|
// Drop the settings view too — the previous detachAllViews() did.
|
|
119
930
|
destroyViewInternal(settingsView);
|
|
@@ -123,6 +934,13 @@ export function createViewManager(ctx) {
|
|
|
123
934
|
simulatorView = null;
|
|
124
935
|
simulatorViewAdded = false;
|
|
125
936
|
simulatorWebContentsId = null;
|
|
937
|
+
// Drop the renderer-published rect so a stale "hidden" override doesn't
|
|
938
|
+
// suppress the next view before its renderer republishes.
|
|
939
|
+
simulatorBoundsOverride = null;
|
|
940
|
+
// Also drop the cached native-simulator rect: attachNativeSimulator replays
|
|
941
|
+
// lastRendererRect on attach, so a leftover rect/offset from a torn-down
|
|
942
|
+
// session must not be replayed onto a fresh re-attach (stale slice/offset).
|
|
943
|
+
lastRendererRect = null;
|
|
126
944
|
}
|
|
127
945
|
function showSimulator(simWidth) {
|
|
128
946
|
lastSimWidth = simWidth;
|
|
@@ -204,6 +1022,8 @@ export function createViewManager(ctx) {
|
|
|
204
1022
|
}
|
|
205
1023
|
}
|
|
206
1024
|
function repositionAll() {
|
|
1025
|
+
// Native simulator: bounds owned by the renderer's reportBounds (Model A);
|
|
1026
|
+
// its window-resize listener re-measures, so no coarse re-apply here.
|
|
207
1027
|
if (simulatorView && simulatorViewAdded)
|
|
208
1028
|
applySimulatorBounds(lastSimWidth);
|
|
209
1029
|
if (settingsView && settingsViewAdded)
|
|
@@ -213,9 +1033,67 @@ export function createViewManager(ctx) {
|
|
|
213
1033
|
}
|
|
214
1034
|
function disposeAll() {
|
|
215
1035
|
detachSimulator();
|
|
1036
|
+
// Host-controllable toolbar view: removed from the contentView + its
|
|
1037
|
+
// WebContents closed (the host's loaded content is torn down on app exit).
|
|
1038
|
+
destroyViewInternal(hostToolbarView);
|
|
1039
|
+
hostToolbarView = null;
|
|
1040
|
+
hostToolbarViewAdded = false;
|
|
1041
|
+
safeArea.dispose();
|
|
1042
|
+
}
|
|
1043
|
+
function setNativeSimulatorViewBounds(params) {
|
|
1044
|
+
// Cache unconditionally so attachNativeSimulator can replay a report that
|
|
1045
|
+
// landed before the view existed (project-open ordering). This rect is the
|
|
1046
|
+
// ONLY authority for the native WCV bounds.
|
|
1047
|
+
lastRendererRect = params;
|
|
1048
|
+
if (ctx.windows.mainWindow.isDestroyed() || !nativeSimulatorView)
|
|
1049
|
+
return;
|
|
1050
|
+
const p = layout.computeNativeSimulatorViewParams(params, params.zoom);
|
|
1051
|
+
currentZoomFactor = p.zoomFactor;
|
|
1052
|
+
// A zero-area rect means "hide" — the renderer reports this when the
|
|
1053
|
+
// simulator panel/cell is toggled off or unmounts (toolbar toggle only
|
|
1054
|
+
// mutates renderer layout state, so without this the WCV would stay
|
|
1055
|
+
// painted over its old region). Mirror `setSimulatorDevtoolsBounds`:
|
|
1056
|
+
// remove the child view from the contentView but keep its WebContents
|
|
1057
|
+
// alive, so re-showing doesn't re-pay the simulator bootstrap.
|
|
1058
|
+
if (isHidden(p.bounds)) {
|
|
1059
|
+
if (nativeSimulatorViewAdded) {
|
|
1060
|
+
try {
|
|
1061
|
+
ctx.windows.mainWindow.contentView.removeChildView(nativeSimulatorView);
|
|
1062
|
+
}
|
|
1063
|
+
catch { /* already removed */ }
|
|
1064
|
+
nativeSimulatorViewAdded = false;
|
|
1065
|
+
}
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
if (!nativeSimulatorViewAdded) {
|
|
1069
|
+
ctx.windows.mainWindow.contentView.addChildView(nativeSimulatorView);
|
|
1070
|
+
nativeSimulatorViewAdded = true;
|
|
1071
|
+
}
|
|
1072
|
+
nativeSimulatorView.setBounds(p.bounds);
|
|
1073
|
+
const simWc = nativeSimulatorView.webContents;
|
|
1074
|
+
if (!simWc.isDestroyed()) {
|
|
1075
|
+
simWc.setZoomFactor(p.zoomFactor);
|
|
1076
|
+
}
|
|
1077
|
+
// Propagate zoom to any already-attached nested render-host guests so the
|
|
1078
|
+
// page rescales live on zoom change (newly-attached guests get it in
|
|
1079
|
+
// did-attach-webview). `webContents.getAllWebContents()` includes guests;
|
|
1080
|
+
// filter to those hosted by this simulator wc.
|
|
1081
|
+
try {
|
|
1082
|
+
for (const wc of webContents.getAllWebContents()) {
|
|
1083
|
+
if (wc.isDestroyed())
|
|
1084
|
+
continue;
|
|
1085
|
+
if (wc.hostWebContents === simWc) {
|
|
1086
|
+
wc.setZoomFactor(p.zoomFactor);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
catch { /* hostWebContents unavailable; guests get zoom on attach */ }
|
|
216
1091
|
}
|
|
217
1092
|
function resize(simWidth) {
|
|
218
1093
|
lastSimWidth = simWidth;
|
|
1094
|
+
// Native simulator bounds are owned solely by the renderer's reportBounds
|
|
1095
|
+
// (Model A) — its ResizeObserver/window-resize listeners re-measure on this
|
|
1096
|
+
// same resize. No coarse panel-size re-apply here (it raced the precise rect).
|
|
219
1097
|
if (simulatorViewAdded)
|
|
220
1098
|
applySimulatorBounds(simWidth);
|
|
221
1099
|
if (settingsViewAdded)
|
|
@@ -234,7 +1112,9 @@ export function createViewManager(ctx) {
|
|
|
234
1112
|
}
|
|
235
1113
|
return {
|
|
236
1114
|
attachSimulator,
|
|
1115
|
+
attachNativeSimulator,
|
|
237
1116
|
detachSimulator,
|
|
1117
|
+
reapplySafeArea: (device) => safeArea.reapplyAll(device),
|
|
238
1118
|
showSimulator,
|
|
239
1119
|
hideSimulator,
|
|
240
1120
|
showSettings,
|
|
@@ -274,8 +1154,20 @@ export function createViewManager(ctx) {
|
|
|
274
1154
|
return null;
|
|
275
1155
|
return popoverView.webContents.id;
|
|
276
1156
|
},
|
|
1157
|
+
getHostToolbarWebContentsId: () => {
|
|
1158
|
+
if (!hostToolbarView)
|
|
1159
|
+
return null;
|
|
1160
|
+
if (hostToolbarView.webContents.isDestroyed())
|
|
1161
|
+
return null;
|
|
1162
|
+
return hostToolbarView.webContents.id;
|
|
1163
|
+
},
|
|
1164
|
+
setNativeSimulatorViewBounds,
|
|
277
1165
|
resize,
|
|
278
1166
|
setVisible,
|
|
1167
|
+
setSimulatorDevtoolsBounds,
|
|
1168
|
+
setHostToolbarBounds,
|
|
1169
|
+
setHostToolbarHeight,
|
|
1170
|
+
hostToolbar,
|
|
279
1171
|
};
|
|
280
1172
|
}
|
|
281
1173
|
//# sourceMappingURL=view-manager.js.map
|