@dimina-kit/devtools 0.3.0 → 0.3.1-dev.20260515105504
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 +103 -41
- package/dist/main/api.d.ts +6 -2
- package/dist/main/api.js +5 -2
- package/dist/main/app/app.d.ts +1 -1
- package/dist/main/app/app.js +71 -36
- package/dist/main/app/launch.d.ts +2 -2
- package/dist/main/app/launch.js +6 -3
- package/dist/main/index.bundle.js +1134 -300
- package/dist/main/ipc/app.d.ts +2 -1
- package/dist/main/ipc/app.js +5 -4
- package/dist/main/ipc/index.d.ts +5 -4
- package/dist/main/ipc/index.js +5 -4
- package/dist/main/ipc/panels.d.ts +2 -1
- package/dist/main/ipc/panels.js +14 -9
- package/dist/main/ipc/popover.d.ts +4 -1
- package/dist/main/ipc/popover.js +15 -5
- package/dist/main/ipc/projects.d.ts +4 -1
- package/dist/main/ipc/projects.js +42 -5
- package/dist/main/ipc/session.d.ts +4 -1
- package/dist/main/ipc/session.js +26 -6
- package/dist/main/ipc/settings.d.ts +4 -1
- package/dist/main/ipc/settings.js +30 -17
- package/dist/main/ipc/simulator-module.d.ts +8 -0
- package/dist/main/ipc/simulator-module.js +19 -0
- package/dist/main/ipc/simulator.d.ts +2 -1
- package/dist/main/ipc/simulator.js +29 -15
- package/dist/main/ipc/toolbar.d.ts +2 -1
- package/dist/main/ipc/toolbar.js +3 -2
- package/dist/main/services/automation/index.js +88 -13
- package/dist/main/services/default-adapter.js +1 -3
- package/dist/main/services/mcp/server.d.ts +2 -1
- package/dist/main/services/mcp/server.js +5 -0
- package/dist/main/services/module.d.ts +18 -0
- package/dist/main/services/module.js +2 -0
- package/dist/main/services/notifications/renderer-notifier.d.ts +2 -0
- package/dist/main/services/projects/project-repository.d.ts +1 -0
- package/dist/main/services/projects/project-repository.js +3 -0
- package/dist/main/services/settings/index.d.ts +4 -0
- package/dist/main/services/settings/index.js +12 -1
- package/dist/main/services/simulator/custom-apis.d.ts +10 -0
- package/dist/main/services/simulator/custom-apis.js +26 -0
- package/dist/main/services/simulator/dir.d.ts +1 -1
- package/dist/main/services/simulator/dir.js +1 -4
- package/dist/main/services/simulator-storage/index.d.ts +22 -1
- package/dist/main/services/simulator-storage/index.js +390 -84
- package/dist/main/services/update/update-manager.d.ts +16 -1
- package/dist/main/services/update/update-manager.js +18 -15
- package/dist/main/services/views/view-manager.d.ts +4 -0
- package/dist/main/services/views/view-manager.js +33 -2
- package/dist/main/services/window-service.d.ts +24 -0
- package/dist/main/services/window-service.js +26 -0
- package/dist/main/services/workbench-context.d.ts +15 -0
- package/dist/main/services/workbench-context.js +6 -0
- package/dist/main/services/workspace/workspace-service.d.ts +1 -0
- package/dist/main/services/workspace/workspace-service.js +7 -3
- package/dist/main/simulator-apis.d.ts +23 -0
- package/dist/main/simulator-apis.js +24 -0
- package/dist/main/utils/disposable.d.ts +13 -0
- package/dist/main/utils/disposable.js +60 -0
- package/dist/main/utils/ipc-registry.d.ts +39 -0
- package/dist/main/utils/ipc-registry.js +109 -0
- package/dist/main/utils/ipc-schema.d.ts +28 -0
- package/dist/main/utils/ipc-schema.js +38 -0
- package/dist/main/utils/paths.d.ts +8 -0
- package/dist/main/utils/paths.js +33 -4
- package/dist/main/utils/sender-policy.d.ts +23 -0
- package/dist/main/utils/sender-policy.js +42 -0
- package/dist/main/windows/main-window/create.d.ts +6 -0
- package/dist/main/windows/main-window/create.js +65 -9
- package/dist/main/windows/main-window/events.d.ts +2 -1
- package/dist/main/windows/main-window/events.js +25 -2
- package/dist/main/windows/navigation-hardening.d.ts +24 -0
- package/dist/main/windows/navigation-hardening.js +55 -0
- package/dist/main/windows/settings-window/create.js +11 -1
- package/dist/preload/index.d.ts +17 -0
- package/dist/preload/index.js +17 -0
- package/dist/preload/instrumentation/app-data.js +178 -19
- package/dist/preload/instrumentation/wxml-extract.js +134 -3
- package/dist/preload/runtime/bridge.d.ts +3 -1
- package/dist/preload/runtime/bridge.js +55 -4
- package/dist/preload/runtime/custom-apis.d.ts +6 -0
- package/dist/preload/runtime/custom-apis.js +18 -0
- package/dist/preload/runtime/host.d.ts +1 -0
- package/dist/preload/runtime/host.js +13 -0
- package/dist/preload/shared/types.d.ts +2 -1
- package/dist/preload/windows/main.cjs +47 -0
- package/dist/preload/windows/main.cjs.map +7 -0
- package/dist/preload/windows/simulator.js +274 -25
- package/dist/renderer/assets/index-DW_0thr-.js +46 -0
- package/dist/renderer/assets/{input-VLotwer2.js → input-C5M5Wxn6.js} +2 -2
- package/dist/renderer/assets/{ipc-transport-COKIC6Ny.js → ipc-transport-CUsl-fS2.js} +6 -6
- package/dist/renderer/assets/ipc-transport-CltRuvhi.css +1 -0
- package/dist/renderer/assets/{popover-Dn0RwMLI.js → popover-BSBPVO0o.js} +2 -2
- package/dist/renderer/assets/{select-DzIsLaKX.js → select-B1tF3UAc.js} +2 -2
- package/dist/renderer/assets/{settings-CD-dacWB.js → settings-7w54H6p-.js} +2 -2
- package/dist/renderer/assets/settings-api-BjJalghB.js +2 -0
- package/dist/renderer/assets/workbenchSettings-q2_-nc8t.js +8 -0
- package/dist/renderer/entries/main/index.html +12 -5
- package/dist/renderer/entries/popover/index.html +7 -5
- package/dist/renderer/entries/settings/index.html +7 -5
- package/dist/renderer/entries/workbench-settings/index.html +6 -4
- package/dist/shared/ipc-channels.d.ts +45 -0
- package/dist/shared/ipc-channels.js +28 -0
- package/dist/shared/ipc-schemas.d.ts +113 -0
- package/dist/shared/ipc-schemas.js +128 -0
- package/dist/shared/types.d.ts +2 -0
- package/dist/simulator/assets/simulator-DL6kOCq-.js +9 -0
- package/dist/simulator/simulator.html +1 -1
- package/package.json +11 -27
- package/dist/preload/windows/main.d.ts +0 -6
- package/dist/preload/windows/main.js +0 -6
- package/dist/renderer/assets/index-D_HFRqZA.js +0 -46
- package/dist/renderer/assets/ipc-transport-6Gy3FXIL.css +0 -1
- package/dist/renderer/assets/settings-api-BV9MPckG.js +0 -2
- package/dist/renderer/assets/workbenchSettings-ClwgsvJL.js +0 -8
- package/dist/simulator/assets/simulator-C1eHXnuq.js +0 -9
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { registerSyntheticSid } from '../runtime/bridge.js';
|
|
1
2
|
const INTERNAL_CLASSES = new Set([
|
|
2
3
|
'dd-swiper-wrapper', 'dd-swiper-slides', 'dd-swiper-slide-frame', 'dd-swiper-dots',
|
|
3
4
|
'dd-picker-overlay', 'dd-picker-container', 'dd-picker-header', 'dd-picker-body',
|
|
@@ -24,10 +25,44 @@ function resolveTemplateNameFromParent(instance) {
|
|
|
24
25
|
}
|
|
25
26
|
return null;
|
|
26
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Convert a PascalCase / camelCase identifier to kebab-case. Matches the
|
|
30
|
+
* upstream `camelCaseToUnderscore` (dimina/fe/packages/common) so a name
|
|
31
|
+
* already normalized by `withInstall` round-trips identically.
|
|
32
|
+
* `View` -> `view`, `ScrollView` -> `scroll-view`, `CoverImage` -> `cover-image`.
|
|
33
|
+
*/
|
|
34
|
+
function pascalToKebab(name) {
|
|
35
|
+
return name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Read the page's source path off Vue's provide/inject. dimina runtime.js 在
|
|
39
|
+
* dd-page 的 setup() 里调用 `provide('path', path)`,Vue 把它存到
|
|
40
|
+
* `instance.provides`。我们利用这个把页面节点的 tagName 从硬编码 `page`
|
|
41
|
+
* 升级为页面全路径(如 `pages/index/index`),对齐微信开发者工具。
|
|
42
|
+
*/
|
|
43
|
+
function resolvePagePath(instance) {
|
|
44
|
+
// dimina runtime 在 dd-page 的 setup 里 `instance.proxy.__page__ = true` 并
|
|
45
|
+
// `provide('path', path)`。两个标记同时具备才视为页面层级,避免 dd-page
|
|
46
|
+
// 的子节点继承 provides.path 后被误判(Vue 用 Object.create 链式 provides)。
|
|
47
|
+
const proxy = instance.proxy;
|
|
48
|
+
if (proxy?.__page__ !== true)
|
|
49
|
+
return null;
|
|
50
|
+
const provides = instance.provides;
|
|
51
|
+
const path = provides?.path;
|
|
52
|
+
if (typeof path !== 'string' || !path)
|
|
53
|
+
return null;
|
|
54
|
+
return path.startsWith('/') ? path.slice(1) : path;
|
|
55
|
+
}
|
|
27
56
|
function resolveTagName(instance) {
|
|
28
57
|
const type = instance.type;
|
|
29
58
|
if (!type)
|
|
30
59
|
return 'unknown';
|
|
60
|
+
// 页面层级优先:dd-page 没有 __tagName/__name,且 home 页等无 usingComponents
|
|
61
|
+
// 的页面 type.components 为 undefined,会落到 resolveTemplateNameFromParent
|
|
62
|
+
// 回到 'page'。在那之前直接用 provide('path') 的路径升级 tag 名。
|
|
63
|
+
const pagePath = resolvePagePath(instance);
|
|
64
|
+
if (pagePath)
|
|
65
|
+
return pagePath;
|
|
31
66
|
if (typeof type.__tagName === 'string')
|
|
32
67
|
return type.__tagName;
|
|
33
68
|
const name = (type.__name || type.name);
|
|
@@ -42,8 +77,16 @@ function resolveTagName(instance) {
|
|
|
42
77
|
return 'page';
|
|
43
78
|
if (name.startsWith('dd-'))
|
|
44
79
|
return name.slice(3);
|
|
80
|
+
// Reverse-map Dimina component names back to their miniprogram tag names.
|
|
81
|
+
// The installer (`withInstall`) sets `__tagName = camelCaseToUnderscore(__name)`,
|
|
82
|
+
// but in dev builds, custom registrations, or when the installer hasn't run,
|
|
83
|
+
// we may only see the raw `__name`/`name` (e.g. `View`, `ScrollView`, `DdButton`).
|
|
84
|
+
// Without this fallback the WXML panel would surface the upstream Vue name
|
|
85
|
+
// verbatim, defeating the panel's purpose of showing source-level tags.
|
|
45
86
|
if (name.startsWith('Dd') && name.length > 2)
|
|
46
|
-
return name.slice(2)
|
|
87
|
+
return pascalToKebab(name.slice(2));
|
|
88
|
+
if (/^[A-Z]/.test(name))
|
|
89
|
+
return pascalToKebab(name);
|
|
47
90
|
return name;
|
|
48
91
|
}
|
|
49
92
|
function extractProps(instance) {
|
|
@@ -79,7 +122,12 @@ function isInternalElement(vnode) {
|
|
|
79
122
|
function getElementSid(instance) {
|
|
80
123
|
const subTree = instance.subTree;
|
|
81
124
|
const el = subTree?.el;
|
|
82
|
-
|
|
125
|
+
if (!el?.getAttribute)
|
|
126
|
+
return undefined;
|
|
127
|
+
const sid = el.getAttribute('data-sid');
|
|
128
|
+
if (sid)
|
|
129
|
+
return sid;
|
|
130
|
+
return registerSyntheticSid(el);
|
|
83
131
|
}
|
|
84
132
|
function isTransparentComponent(instance, tagName) {
|
|
85
133
|
if (tagName === 'unknown')
|
|
@@ -97,9 +145,29 @@ function isTransparentComponent(instance, tagName) {
|
|
|
97
145
|
}
|
|
98
146
|
return false;
|
|
99
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* 检测 Vue 的 Comment vnode(`v-if`/`v-else`/`v-for` 占位锚点)。
|
|
150
|
+
* Vue 在条件不成立时会留下 `<!-- v-if -->` 这样的注释 vnode 用作 DOM 锚点。
|
|
151
|
+
* 这些 vnode 不是用户内容,必须从 wxml 树里剔除。
|
|
152
|
+
*
|
|
153
|
+
* - dev build:type 是 `Symbol('Comment')`,description = 'Comment' 或 'v-cmt'
|
|
154
|
+
* - prod build:description 可能丢失,但 children 一般是 'v-if'/'v-else' 等 marker
|
|
155
|
+
*/
|
|
156
|
+
function isCommentVNode(vnode) {
|
|
157
|
+
const type = vnode.type;
|
|
158
|
+
if (typeof type !== 'symbol')
|
|
159
|
+
return false;
|
|
160
|
+
const desc = type.description;
|
|
161
|
+
if (desc === 'Comment' || desc === 'v-cmt')
|
|
162
|
+
return true;
|
|
163
|
+
const children = typeof vnode.children === 'string' ? vnode.children : '';
|
|
164
|
+
return /^v-(if|else|else-if|for)$/.test(children);
|
|
165
|
+
}
|
|
100
166
|
function extractChildrenFromVNode(vnode, depth) {
|
|
101
167
|
if (!vnode || depth > 50)
|
|
102
168
|
return [];
|
|
169
|
+
if (isCommentVNode(vnode))
|
|
170
|
+
return [];
|
|
103
171
|
if (vnode.component) {
|
|
104
172
|
const result = walkInstance(vnode.component, depth + 1);
|
|
105
173
|
return result ? (Array.isArray(result) ? result : [result]) : [];
|
|
@@ -157,6 +225,69 @@ function extractChildrenFromVNode(vnode, depth) {
|
|
|
157
225
|
}
|
|
158
226
|
return result;
|
|
159
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* Reverse-map Dimina's `<wrapper name="/components/foo/foo">` (used to host
|
|
230
|
+
* every user-defined component) back to the source-level tag the user wrote
|
|
231
|
+
* in WXML (e.g. `<foo>`).
|
|
232
|
+
*
|
|
233
|
+
* The wrapper carries the registered component path in its `name` Vue prop.
|
|
234
|
+
* By convention (also followed by miniprogram tooling), the registered key is
|
|
235
|
+
* the last path segment, optionally stripping a trailing `/index`. We can't
|
|
236
|
+
* see the page's `usingComponents` map from inside the Vue instance, so this
|
|
237
|
+
* convention-based recovery is best-effort: if the user picked a different
|
|
238
|
+
* registration key than the directory name (e.g. `usingComponents:
|
|
239
|
+
* { myCounter: '/components/counter/counter' }`), the panel will show
|
|
240
|
+
* `counter`, not `myCounter`.
|
|
241
|
+
*
|
|
242
|
+
* The path heuristic also resolves a name-collision risk: a user-written
|
|
243
|
+
* `<counter name="x">` would land in the same `attrs.name` slot as the
|
|
244
|
+
* wrapper-internal path. We only unwrap when the value looks like an absolute
|
|
245
|
+
* component path (leading `/`), which dimina always emits but a user would
|
|
246
|
+
* almost never type as a literal attr.
|
|
247
|
+
*/
|
|
248
|
+
function unwrapCustomComponent(node) {
|
|
249
|
+
if (node.tagName !== 'wrapper')
|
|
250
|
+
return node;
|
|
251
|
+
const path = node.attrs?.name;
|
|
252
|
+
if (typeof path !== 'string' || !path.startsWith('/'))
|
|
253
|
+
return node;
|
|
254
|
+
// 去掉前导 `/` 后保留原路径形式(保留所有斜杠,不做 kebab/dash 转换)。
|
|
255
|
+
// 仅当路径以 `/index` 结尾且剥离后仍至少剩一段时才剥(`/index` 单独存在则
|
|
256
|
+
// 退回 wrapper,避免出现空 tagName)。
|
|
257
|
+
const stripped = path.replace(/^\//, '');
|
|
258
|
+
const withoutIndex = stripped.endsWith('/index') ? stripped.slice(0, -'/index'.length) : stripped;
|
|
259
|
+
const recovered = withoutIndex || stripped;
|
|
260
|
+
if (!recovered || recovered === 'index')
|
|
261
|
+
return node;
|
|
262
|
+
const nextAttrs = {};
|
|
263
|
+
for (const [k, v] of Object.entries(node.attrs)) {
|
|
264
|
+
if (k === 'name')
|
|
265
|
+
continue;
|
|
266
|
+
nextAttrs[k] = v;
|
|
267
|
+
}
|
|
268
|
+
return { ...node, tagName: recovered, attrs: nextAttrs };
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* 把"用户授权"层级(页面 / 自定义组件,tagName 以路径形式呈现,含 `/`)
|
|
272
|
+
* 的 children 包一层合成 `#shadow-root`,对齐微信开发者工具:组件本身
|
|
273
|
+
* 与内部实现之间用 shadow-root 边界视觉分隔。
|
|
274
|
+
*
|
|
275
|
+
* 内置组件(view/text/button 等)和合成节点(#text/#fragment)tagName 都
|
|
276
|
+
* 不含 `/`,自然不会被包裹。children 为空也不插入(避免空壳)。重复调用
|
|
277
|
+
* 是幂等的:若 children[0] 已经是 #shadow-root 就跳过。
|
|
278
|
+
*/
|
|
279
|
+
function wrapInShadowRoot(node) {
|
|
280
|
+
if (!node.tagName.includes('/'))
|
|
281
|
+
return node;
|
|
282
|
+
if (node.children.length === 0)
|
|
283
|
+
return node;
|
|
284
|
+
if (node.children[0]?.tagName === '#shadow-root')
|
|
285
|
+
return node;
|
|
286
|
+
return {
|
|
287
|
+
...node,
|
|
288
|
+
children: [{ tagName: '#shadow-root', attrs: {}, children: node.children }],
|
|
289
|
+
};
|
|
290
|
+
}
|
|
160
291
|
export function walkInstance(instance, depth) {
|
|
161
292
|
if (depth > 50)
|
|
162
293
|
return null;
|
|
@@ -168,6 +299,6 @@ export function walkInstance(instance, depth) {
|
|
|
168
299
|
const sid = getElementSid(instance);
|
|
169
300
|
if (sid)
|
|
170
301
|
node.sid = sid;
|
|
171
|
-
return node;
|
|
302
|
+
return wrapInShadowRoot(unwrapCustomComponent(node));
|
|
172
303
|
}
|
|
173
304
|
//# sourceMappingURL=wxml-extract.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ElementInspection } from '../../shared/ipc-channels.js';
|
|
1
2
|
export interface WxmlNode {
|
|
2
3
|
tagName: string;
|
|
3
4
|
attrs: Record<string, string>;
|
|
@@ -20,7 +21,8 @@ interface SimulatorBridgeState {
|
|
|
20
21
|
wxml: Snapshot<WxmlNode | WxmlNode[] | null>;
|
|
21
22
|
refreshHandler: ((type: RefreshTarget) => void) | null;
|
|
22
23
|
}
|
|
23
|
-
declare function
|
|
24
|
+
export declare function registerSyntheticSid(el: HTMLElement): string;
|
|
25
|
+
declare function highlightElement(sid: string): ElementInspection | null;
|
|
24
26
|
declare function unhighlightElement(): void;
|
|
25
27
|
export { highlightElement, unhighlightElement };
|
|
26
28
|
export declare const simulatorBridge: SimulatorBridgeState;
|
|
@@ -7,6 +7,38 @@ const state = {
|
|
|
7
7
|
};
|
|
8
8
|
let highlightOverlay = null;
|
|
9
9
|
let exposedApi = null;
|
|
10
|
+
// 合成 sid 注册表:用 WeakMap 把元素 ↔ sid 双向绑定,避免在源 DOM 上写
|
|
11
|
+
// `data-*` 属性(提取本应只读,且属性形式会污染用户的快照/选择器)。
|
|
12
|
+
// elBySyntheticSid 为反向查找用 WeakRef,元素被 GC 后下次 lookup 自动清理。
|
|
13
|
+
const SYNTHETIC_SID_PREFIX = 'devtools-';
|
|
14
|
+
const syntheticSidByEl = new WeakMap();
|
|
15
|
+
const elBySyntheticSid = new Map();
|
|
16
|
+
let nextSyntheticSid = 1;
|
|
17
|
+
export function registerSyntheticSid(el) {
|
|
18
|
+
const existing = syntheticSidByEl.get(el);
|
|
19
|
+
if (existing)
|
|
20
|
+
return existing;
|
|
21
|
+
const synthetic = `${SYNTHETIC_SID_PREFIX}${nextSyntheticSid++}`;
|
|
22
|
+
syntheticSidByEl.set(el, synthetic);
|
|
23
|
+
elBySyntheticSid.set(synthetic, new WeakRef(el));
|
|
24
|
+
return synthetic;
|
|
25
|
+
}
|
|
26
|
+
function findElementBySid(doc, sid) {
|
|
27
|
+
if (sid.startsWith(SYNTHETIC_SID_PREFIX)) {
|
|
28
|
+
const ref = elBySyntheticSid.get(sid);
|
|
29
|
+
if (!ref)
|
|
30
|
+
return null;
|
|
31
|
+
const el = ref.deref();
|
|
32
|
+
if (!el || !el.isConnected) {
|
|
33
|
+
elBySyntheticSid.delete(sid);
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
if (el.ownerDocument !== doc)
|
|
37
|
+
return null;
|
|
38
|
+
return el;
|
|
39
|
+
}
|
|
40
|
+
return doc.querySelector(`[data-sid="${CSS.escape(sid)}"]`);
|
|
41
|
+
}
|
|
10
42
|
function clone(value) {
|
|
11
43
|
try {
|
|
12
44
|
return JSON.parse(JSON.stringify(value));
|
|
@@ -27,18 +59,20 @@ function ensureOverlay(doc) {
|
|
|
27
59
|
highlightOverlay.style.cssText =
|
|
28
60
|
'position:fixed;pointer-events:none;z-index:999999;' +
|
|
29
61
|
'border:2px solid #1a73e8;background:rgba(26,115,232,0.12);' +
|
|
30
|
-
'transition:all 0.1s ease;display:none;border-radius:2px;';
|
|
62
|
+
'transition:all 0.1s ease;display:none;border-radius:2px;box-sizing:border-box;';
|
|
31
63
|
doc.body.appendChild(highlightOverlay);
|
|
32
64
|
return highlightOverlay;
|
|
33
65
|
}
|
|
34
66
|
function highlightElement(sid) {
|
|
67
|
+
if (!sid)
|
|
68
|
+
return null;
|
|
35
69
|
const iframe = getPageIframe();
|
|
36
70
|
if (!iframe?.contentDocument)
|
|
37
|
-
return;
|
|
71
|
+
return null;
|
|
38
72
|
const doc = iframe.contentDocument;
|
|
39
|
-
const el = doc
|
|
73
|
+
const el = findElementBySid(doc, sid);
|
|
40
74
|
if (!el)
|
|
41
|
-
return;
|
|
75
|
+
return null;
|
|
42
76
|
const rect = el.getBoundingClientRect();
|
|
43
77
|
const overlay = ensureOverlay(doc);
|
|
44
78
|
overlay.style.left = `${rect.left}px`;
|
|
@@ -46,6 +80,23 @@ function highlightElement(sid) {
|
|
|
46
80
|
overlay.style.width = `${rect.width}px`;
|
|
47
81
|
overlay.style.height = `${rect.height}px`;
|
|
48
82
|
overlay.style.display = 'block';
|
|
83
|
+
const style = el.ownerDocument.defaultView?.getComputedStyle(el);
|
|
84
|
+
if (!style)
|
|
85
|
+
return null;
|
|
86
|
+
return {
|
|
87
|
+
sid,
|
|
88
|
+
rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
|
|
89
|
+
style: {
|
|
90
|
+
display: style.display,
|
|
91
|
+
position: style.position,
|
|
92
|
+
boxSizing: style.boxSizing,
|
|
93
|
+
margin: style.margin,
|
|
94
|
+
padding: style.padding,
|
|
95
|
+
color: style.color,
|
|
96
|
+
backgroundColor: style.backgroundColor,
|
|
97
|
+
fontSize: style.fontSize,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
49
100
|
}
|
|
50
101
|
function unhighlightElement() {
|
|
51
102
|
if (highlightOverlay)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { contextBridge, ipcRenderer } from 'electron';
|
|
2
|
+
import { SimulatorCustomApiChannel } from '../../shared/ipc-channels.js';
|
|
3
|
+
function buildBridge() {
|
|
4
|
+
return {
|
|
5
|
+
list: () => ipcRenderer.invoke(SimulatorCustomApiChannel.List),
|
|
6
|
+
invoke: (name, params) => ipcRenderer.invoke(SimulatorCustomApiChannel.Invoke, name, params),
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export function installCustomApisBridge() {
|
|
10
|
+
const bridge = buildBridge();
|
|
11
|
+
try {
|
|
12
|
+
contextBridge.exposeInMainWorld('__diminaCustomApis', bridge);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
window.__diminaCustomApis = bridge;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=custom-apis.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export declare function sendToHost(channel: string, data: unknown): void;
|
|
2
|
+
export declare function onHostMessage(channel: string, handler: (...args: unknown[]) => void): () => void;
|
|
2
3
|
export declare function safeSerialize(val: unknown): unknown;
|
|
3
4
|
//# sourceMappingURL=host.d.ts.map
|
|
@@ -2,6 +2,19 @@ import { ipcRenderer } from 'electron';
|
|
|
2
2
|
export function sendToHost(channel, data) {
|
|
3
3
|
ipcRenderer.sendToHost(channel, data);
|
|
4
4
|
}
|
|
5
|
+
export function onHostMessage(channel, handler) {
|
|
6
|
+
let active = true;
|
|
7
|
+
const wrapped = (_event, ...args) => {
|
|
8
|
+
if (!active)
|
|
9
|
+
return;
|
|
10
|
+
handler(...args);
|
|
11
|
+
};
|
|
12
|
+
ipcRenderer.on(channel, wrapped);
|
|
13
|
+
return () => {
|
|
14
|
+
active = false;
|
|
15
|
+
ipcRenderer.removeListener(channel, wrapped);
|
|
16
|
+
};
|
|
17
|
+
}
|
|
5
18
|
export function safeSerialize(val) {
|
|
6
19
|
if (val === null || val === undefined)
|
|
7
20
|
return val;
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* window by our preload/instrumentation code.
|
|
6
6
|
*/
|
|
7
7
|
import type { Snapshot, StorageSnapshot, WxmlNode } from '../runtime/bridge.js';
|
|
8
|
+
import type { ElementInspection } from '../../shared/ipc-channels.js';
|
|
8
9
|
/** The simulator data bridge exposed via contextBridge. */
|
|
9
10
|
export interface SimulatorDataBridge {
|
|
10
11
|
getAppdata: () => Record<string, unknown>;
|
|
@@ -16,7 +17,7 @@ export interface SimulatorDataBridge {
|
|
|
16
17
|
getWxmlSnapshot: () => Snapshot<WxmlNode | WxmlNode[] | null>;
|
|
17
18
|
getWxmlGen: () => number;
|
|
18
19
|
refresh: (type: 'wxml' | 'appdata' | 'storage') => void;
|
|
19
|
-
highlightElement: (sid: string) =>
|
|
20
|
+
highlightElement: (sid: string) => ElementInspection | null;
|
|
20
21
|
unhighlightElement: () => void;
|
|
21
22
|
}
|
|
22
23
|
/** Hook interface for app data interception. */
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __copyProps = (to, from, except, desc) => {
|
|
7
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
8
|
+
for (let key of __getOwnPropNames(from))
|
|
9
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
10
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
11
|
+
}
|
|
12
|
+
return to;
|
|
13
|
+
};
|
|
14
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
15
|
+
|
|
16
|
+
// src/preload/windows/main.ts
|
|
17
|
+
var main_exports = {};
|
|
18
|
+
module.exports = __toCommonJS(main_exports);
|
|
19
|
+
var import_electron = require("electron");
|
|
20
|
+
var api = {
|
|
21
|
+
ipc: {
|
|
22
|
+
invoke: (channel, ...args) => import_electron.ipcRenderer.invoke(channel, ...args),
|
|
23
|
+
send: (channel, ...args) => {
|
|
24
|
+
import_electron.ipcRenderer.send(channel, ...args);
|
|
25
|
+
},
|
|
26
|
+
on: (channel, listener) => {
|
|
27
|
+
import_electron.ipcRenderer.on(channel, listener);
|
|
28
|
+
return () => import_electron.ipcRenderer.removeListener(channel, listener);
|
|
29
|
+
},
|
|
30
|
+
once: (channel, listener) => {
|
|
31
|
+
import_electron.ipcRenderer.once(channel, listener);
|
|
32
|
+
},
|
|
33
|
+
removeListener: (channel, listener) => {
|
|
34
|
+
import_electron.ipcRenderer.removeListener(channel, listener);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
import_electron.contextBridge.exposeInMainWorld("devtools", api);
|
|
39
|
+
if (process.env.NODE_ENV === "test") {
|
|
40
|
+
import_electron.contextBridge.exposeInMainWorld("__testIpc", {
|
|
41
|
+
emit: (channel, ...args) => {
|
|
42
|
+
;
|
|
43
|
+
import_electron.ipcRenderer.emit(channel, ...args);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=main.cjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/preload/windows/main.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Preload bridge for the main window, settings window, settings overlay\n * view, and popover overlay view. Exposes a minimal, typed `window.devtools`\n * surface so the renderer never has to call `window.require('electron')`.\n *\n * This file is bundled by esbuild to CJS at `dist/preload/windows/main.js`\n * (see package.json `build:preload`). It must remain a leaf module \u2014 do\n * NOT import other preload files via `.js` ESM specifiers; everything goes\n * through the Electron `contextBridge` / `ipcRenderer` runtime.\n *\n * Channel governance lives in the main process: `sender-policy.ts` plus the\n * per-handler zod schemas decide what's accepted. The preload layer is a\n * thin pass-through \u2014 a renderer inventing channel names will just hit a\n * main-side rejection, so duplicating an allowlist here added no real\n * defense for a devtool that loads user-trusted mini-programs.\n */\nimport { contextBridge, ipcRenderer, type IpcRendererEvent } from 'electron'\n\nconst api = {\n ipc: {\n invoke: (channel: string, ...args: unknown[]) => ipcRenderer.invoke(channel, ...args),\n send: (channel: string, ...args: unknown[]) => {\n ipcRenderer.send(channel, ...args)\n },\n on: (\n channel: string,\n listener: (event: IpcRendererEvent, ...args: unknown[]) => void,\n ) => {\n ipcRenderer.on(channel, listener)\n return () => ipcRenderer.removeListener(channel, listener)\n },\n once: (\n channel: string,\n listener: (event: IpcRendererEvent, ...args: unknown[]) => void,\n ) => {\n ipcRenderer.once(channel, listener)\n },\n removeListener: (\n channel: string,\n listener: (...args: unknown[]) => void,\n ) => {\n ipcRenderer.removeListener(channel, listener)\n },\n },\n}\n\n// All host windows that load this preload run with `contextIsolation: true`\n// (main / settings / view-manager), so `contextBridge` is the path that\n// reaches the renderer's `window`. The simulator <webview> uses a separate\n// preload (`simulator.ts`).\ncontextBridge.exposeInMainWorld('devtools', api)\n\n// Test-only hatch: e2e helpers emit synthetic IPC events on the renderer's\n// ipcRenderer instance to trigger handlers registered via `devtools.ipc.on`\n// without round-tripping through the main process. Gated on NODE_ENV so\n// production builds never expose it.\nif (process.env.NODE_ENV === 'test') {\n contextBridge.exposeInMainWorld('__testIpc', {\n emit: (channel: string, ...args: unknown[]) => {\n ;(ipcRenderer as unknown as { emit: (c: string, ...a: unknown[]) => void }).emit(channel, ...args)\n },\n })\n}\n\nexport type DevtoolsApi = typeof api\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;AAAA;AAAA;AAgBA,sBAAkE;AAElE,IAAM,MAAM;AAAA,EACV,KAAK;AAAA,IACH,QAAQ,CAAC,YAAoB,SAAoB,4BAAY,OAAO,SAAS,GAAG,IAAI;AAAA,IACpF,MAAM,CAAC,YAAoB,SAAoB;AAC7C,kCAAY,KAAK,SAAS,GAAG,IAAI;AAAA,IACnC;AAAA,IACA,IAAI,CACF,SACA,aACG;AACH,kCAAY,GAAG,SAAS,QAAQ;AAChC,aAAO,MAAM,4BAAY,eAAe,SAAS,QAAQ;AAAA,IAC3D;AAAA,IACA,MAAM,CACJ,SACA,aACG;AACH,kCAAY,KAAK,SAAS,QAAQ;AAAA,IACpC;AAAA,IACA,gBAAgB,CACd,SACA,aACG;AACH,kCAAY,eAAe,SAAS,QAAQ;AAAA,IAC9C;AAAA,EACF;AACF;AAMA,8BAAc,kBAAkB,YAAY,GAAG;AAM/C,IAAI,QAAQ,IAAI,aAAa,QAAQ;AACnC,gCAAc,kBAAkB,aAAa;AAAA,IAC3C,MAAM,CAAC,YAAoB,SAAoB;AAC7C;AAAC,MAAC,4BAA0E,KAAK,SAAS,GAAG,IAAI;AAAA,IACnG;AAAA,EACF,CAAC;AACH;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|