@dimina-kit/devtools 0.4.0-dev.20260630070008 → 0.4.0-dev.20260630123203
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main/index.bundle.js +158 -10
- package/dist/main/ipc/app-lifecycle.d.ts +32 -0
- package/dist/main/ipc/app-lifecycle.js +54 -0
- package/dist/main/ipc/bridge-router.js +173 -10
- package/dist/main/ipc/page-scroll.d.ts +14 -0
- package/dist/main/ipc/page-scroll.js +17 -0
- package/dist/main/windows/service-host-window/create.d.ts +7 -0
- package/dist/main/windows/service-host-window/create.js +5 -0
- package/dist/preload/windows/main.cjs +200 -0
- package/dist/preload/windows/main.cjs.map +3 -3
- package/dist/service-host/preload.cjs +11 -0
- package/dist/simulator/assets/{device-shell-BiFE9Otv.css → device-shell-CS1rqAQz.css} +1 -1
- package/dist/simulator/assets/device-shell-H3Bq5NgA.js +2 -0
- package/dist/simulator/assets/{jsx-runtime-P6ZegaOf.js → jsx-runtime-CDK-o-S0.js} +2 -2
- package/dist/simulator/assets/simulator-CYPhufF2.js +10 -0
- package/dist/simulator/simulator.html +2 -2
- package/package.json +5 -5
- package/dist/simulator/assets/device-shell-CPCnCp1L.js +0 -2
- package/dist/simulator/assets/simulator-gMBWKDeO.js +0 -10
|
@@ -11,6 +11,8 @@ import { ServiceHostPool } from '../services/service-host-pool/pool.js';
|
|
|
11
11
|
import { registerMiniappSessionConfigurator, SHARED_MINIAPP_PARTITION, } from '../services/views/miniapp-partition.js';
|
|
12
12
|
import { createConsoleForwarder } from '../services/console-forward/index.js';
|
|
13
13
|
import { STORAGE_API_NAMES } from '../services/simulator-storage/index.js';
|
|
14
|
+
import { buildPageScrollScript } from './page-scroll.js';
|
|
15
|
+
import { createAppLifecycleController, } from './app-lifecycle.js';
|
|
14
16
|
// The compiled `logic.js` ships a RELATIVE `//# sourceMappingURL=logic.js.map`.
|
|
15
17
|
// `injectLogicBundle` loads it via `executeJavaScript`, which gives the injected
|
|
16
18
|
// script no base URL of its own — so DevTools resolves that relative map against
|
|
@@ -152,6 +154,7 @@ export function installBridgeRouter(ctx) {
|
|
|
152
154
|
emitRenderEvent: () => { },
|
|
153
155
|
connections: ctx.connections,
|
|
154
156
|
debugTap: createDebugTap({ enabled: resolveDebugTapEnabled() }),
|
|
157
|
+
appLifecycle: createAppLifecycleController(),
|
|
155
158
|
evictAppDataBridges: (ap) => {
|
|
156
159
|
if (!ctx.appData)
|
|
157
160
|
return;
|
|
@@ -180,6 +183,7 @@ export function installBridgeRouter(ctx) {
|
|
|
180
183
|
ctx.registry.add(() => clearTimeout(warmTimer));
|
|
181
184
|
ctx.registry.add(() => state.pool?.dispose());
|
|
182
185
|
}
|
|
186
|
+
installAppLifecycleDriver(ctx, state);
|
|
183
187
|
installResourceProtocolHandlers(ctx, state);
|
|
184
188
|
// The currently-selected device (renderer toolbar). Cached here so it can ride
|
|
185
189
|
// the NATIVE_HOST_ENABLED reply (race-free DeviceShell init) and so the
|
|
@@ -490,6 +494,11 @@ async function handleSpawn(state, ctx, event, opts) {
|
|
|
490
494
|
: '';
|
|
491
495
|
const pkgRoot = path.resolve(opts.pkgRoot || workspaceProjectPath || process.cwd());
|
|
492
496
|
const root = opts.root || 'main';
|
|
497
|
+
// Host-config custom API namespaces (WorkbenchContext is the single owner).
|
|
498
|
+
// Threaded into the service-host spawn URL so its preload can install the
|
|
499
|
+
// namespace globals; the simulator-supplied `opts.apiNamespaces` is derived
|
|
500
|
+
// from the same host config and is not authoritative here.
|
|
501
|
+
const apiNamespaces = ctx.apiNamespaces ?? [];
|
|
493
502
|
// Resource base resolution. Preferred: the simulator-supplied dev-server
|
|
494
503
|
// origin, which statically serves the compiled `<appId>/<root>/…` tree (same
|
|
495
504
|
// source the default dimina-fe `<webview>` reads). Fallback (no dev server —
|
|
@@ -550,6 +559,7 @@ async function handleSpawn(state, ctx, event, opts) {
|
|
|
550
559
|
root,
|
|
551
560
|
resourceBaseUrl,
|
|
552
561
|
hostEnvSnapshot: hostEnv,
|
|
562
|
+
apiNamespaces,
|
|
553
563
|
});
|
|
554
564
|
}
|
|
555
565
|
const appSession = {
|
|
@@ -562,6 +572,7 @@ async function handleSpawn(state, ctx, event, opts) {
|
|
|
562
572
|
serviceWc: serviceWindow.webContents,
|
|
563
573
|
simulatorWc,
|
|
564
574
|
serviceLoaded: false,
|
|
575
|
+
logicInjected: null,
|
|
565
576
|
resourceBaseUrl,
|
|
566
577
|
resourceServer,
|
|
567
578
|
hostEnv,
|
|
@@ -583,6 +594,7 @@ async function handleSpawn(state, ctx, event, opts) {
|
|
|
583
594
|
renderWc: null,
|
|
584
595
|
renderLoaded: false,
|
|
585
596
|
resourceLoadedSent: false,
|
|
597
|
+
renderLoadPending: false,
|
|
586
598
|
windowConfig: rootWindowConfig,
|
|
587
599
|
};
|
|
588
600
|
state.appSessions.set(appSessionId, appSession);
|
|
@@ -653,7 +665,7 @@ async function handleSpawn(state, ctx, event, opts) {
|
|
|
653
665
|
if (!serviceWindow.webContents.getURL().includes('service.html'))
|
|
654
666
|
return;
|
|
655
667
|
serviceWindow.webContents.removeListener('did-finish-load', bootOnServiceLoad);
|
|
656
|
-
void bootServiceHost(state, appSession);
|
|
668
|
+
void bootServiceHost(state, appSession, ctx);
|
|
657
669
|
};
|
|
658
670
|
appSession.onServiceBoot = bootOnServiceLoad;
|
|
659
671
|
serviceWindow.webContents.on('did-finish-load', bootOnServiceLoad);
|
|
@@ -665,13 +677,14 @@ async function handleSpawn(state, ctx, event, opts) {
|
|
|
665
677
|
root,
|
|
666
678
|
resourceBaseUrl,
|
|
667
679
|
hostEnvSnapshot: hostEnv,
|
|
680
|
+
apiNamespaces,
|
|
668
681
|
}));
|
|
669
682
|
}
|
|
670
683
|
else {
|
|
671
684
|
// Fresh window: its only navigation is service.html (issued inside
|
|
672
685
|
// createServiceHostWindow), so the first did-finish-load is the spawn load.
|
|
673
686
|
serviceWindow.webContents.once('did-finish-load', () => {
|
|
674
|
-
void bootServiceHost(state, appSession);
|
|
687
|
+
void bootServiceHost(state, appSession, ctx);
|
|
675
688
|
});
|
|
676
689
|
}
|
|
677
690
|
return {
|
|
@@ -707,6 +720,7 @@ async function handlePageOpen(state, event, opts) {
|
|
|
707
720
|
renderWc: null,
|
|
708
721
|
renderLoaded: false,
|
|
709
722
|
resourceLoadedSent: false,
|
|
723
|
+
renderLoadPending: false,
|
|
710
724
|
windowConfig,
|
|
711
725
|
};
|
|
712
726
|
state.pageSessions.set(bridgeId, page);
|
|
@@ -761,26 +775,53 @@ function handleNavCallback(state, sender, payload) {
|
|
|
761
775
|
sendCallback(ap, payload.callbacks.complete, result);
|
|
762
776
|
}
|
|
763
777
|
// ── Service-host boot & per-page resource handshake ──────────────────────────
|
|
764
|
-
async function bootServiceHost(state, ap) {
|
|
778
|
+
async function bootServiceHost(state, ap, ctx) {
|
|
765
779
|
// Liveness guard: never boot a session that was already disposed. With pooling,
|
|
766
780
|
// the service window is recycled, so a stale did-finish-load listener from an
|
|
767
781
|
// early-disposed prior owner could otherwise fire here and inject the wrong
|
|
768
782
|
// app's logic.js into the next spawn (the recycled webContents is shared).
|
|
769
783
|
if (state.appSessions.get(ap.appSessionId) !== ap)
|
|
770
784
|
return;
|
|
771
|
-
await injectLogicBundle(ap);
|
|
785
|
+
ap.logicInjected = await injectLogicBundle(ap);
|
|
786
|
+
if (!ap.logicInjected) {
|
|
787
|
+
// The compiled logic.js never executed, so `modDefine` registered nothing.
|
|
788
|
+
// Sending `loadResource` now would run `modRequire('app')` against an empty
|
|
789
|
+
// registry and throw the cryptic `module app not found` — masking the real
|
|
790
|
+
// cause (unreachable resource tree / failed compile / unresolved appId).
|
|
791
|
+
// Skip it and surface one actionable diagnostic instead; the render side
|
|
792
|
+
// gates on the same flag in `routeFromRender`.
|
|
793
|
+
reportLogicLoadFailure(ap, ctx);
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
772
796
|
// serviceLoaded is flipped only when service responds with
|
|
773
797
|
// `serviceResourceLoaded`; see handleContainerMsg. Setting it here would
|
|
774
798
|
// race a per-page `resourceLoaded` ahead of the service-side handler.
|
|
775
799
|
forwardToService(ap, makeLoadResource(ap, ap.pages.get(ap.appSessionId), 'service'));
|
|
800
|
+
// Flush any render `loadResource` that arrived (renderHostReady) while
|
|
801
|
+
// injection was in-flight — now that the bundle is confirmed present.
|
|
802
|
+
for (const page of ap.pages.values()) {
|
|
803
|
+
if (page.renderLoadPending) {
|
|
804
|
+
page.renderLoadPending = false;
|
|
805
|
+
sendRenderLoadResource(ap, page);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
function sendRenderLoadResource(ap, page) {
|
|
810
|
+
if (page.renderWc && !page.renderWc.isDestroyed()) {
|
|
811
|
+
page.renderWc.send(C.TO_RENDER, { msg: makeLoadResource(ap, page, 'render') });
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
/** The compiled logic.js URL `injectLogicBundle` fetches (mode-dependent). */
|
|
815
|
+
function logicBundleUrl(ap) {
|
|
816
|
+
return ap.resourceServer
|
|
817
|
+
? new URL('logic.js', ap.resourceBaseUrl).toString()
|
|
818
|
+
: new URL(`${ap.appId}/${ap.root}/logic.js`, ap.resourceBaseUrl).toString();
|
|
776
819
|
}
|
|
777
820
|
async function injectLogicBundle(ap) {
|
|
778
821
|
// Fetch the compiled service logic over HTTP from the same base the render
|
|
779
822
|
// host reads (`<base><appId>/<root>/logic.js`). The fallback local server
|
|
780
823
|
// serves its root, so `<base>logic.js` resolves there too — both are http.
|
|
781
|
-
const logicUrl = ap
|
|
782
|
-
? new URL('logic.js', ap.resourceBaseUrl).toString()
|
|
783
|
-
: new URL(`${ap.appId}/${ap.root}/logic.js`, ap.resourceBaseUrl).toString();
|
|
824
|
+
const logicUrl = logicBundleUrl(ap);
|
|
784
825
|
try {
|
|
785
826
|
const res = await fetch(logicUrl);
|
|
786
827
|
if (!res.ok)
|
|
@@ -790,11 +831,31 @@ async function injectLogicBundle(ap) {
|
|
|
790
831
|
// relative map would 404 and break sourcemapped console frames / Sources.
|
|
791
832
|
const logicContent = rewriteSourceMappingUrl(await res.text(), logicUrl);
|
|
792
833
|
await ap.serviceWc.executeJavaScript(`${logicContent}\n//# sourceURL=${logicUrl}`, true);
|
|
834
|
+
return true;
|
|
793
835
|
}
|
|
794
836
|
catch (error) {
|
|
795
837
|
console.warn('[bridge-router] unable to inject service logic.js:', error);
|
|
838
|
+
return false;
|
|
796
839
|
}
|
|
797
840
|
}
|
|
841
|
+
/**
|
|
842
|
+
* Emit one actionable diagnostic when the compiled logic bundle could not be
|
|
843
|
+
* loaded — the single point where "the app's compiled resource tree is
|
|
844
|
+
* unreachable" is known with certainty. Goes to the main-process log AND the
|
|
845
|
+
* guest console sink so it surfaces in the devtools Console panel where the
|
|
846
|
+
* developer would otherwise see only the misleading `module app not found`.
|
|
847
|
+
*/
|
|
848
|
+
function reportLogicLoadFailure(ap, ctx) {
|
|
849
|
+
const hint = ap.appId === 'unknown'
|
|
850
|
+
? ' appId could not be resolved (it fell back to "unknown") — the mini-program likely failed to compile or its project manifest/app config is missing.'
|
|
851
|
+
: '';
|
|
852
|
+
const message = `[dimina-kit] Failed to load the mini-program logic bundle from ${logicBundleUrl(ap)}. `
|
|
853
|
+
+ 'The service runtime has no registered modules, so no page can mount.'
|
|
854
|
+
+ hint
|
|
855
|
+
+ ` Verify the project compiled successfully and that the resource server serves "${ap.appId}/${ap.root}/".`;
|
|
856
|
+
console.error(message);
|
|
857
|
+
ctx.guestConsole?.emit({ source: 'service', level: 'error', args: [message] });
|
|
858
|
+
}
|
|
798
859
|
function maybeSendResourceLoaded(ap, page) {
|
|
799
860
|
if (page.resourceLoadedSent || !ap.serviceLoaded || !page.renderLoaded)
|
|
800
861
|
return;
|
|
@@ -842,9 +903,21 @@ function routeFromService(state, ap, defaultPage, msg, ctx) {
|
|
|
842
903
|
}
|
|
843
904
|
function routeFromRender(state, ap, page, msg, ctx) {
|
|
844
905
|
if (msg.type === 'renderHostReady') {
|
|
845
|
-
|
|
846
|
-
|
|
906
|
+
// Logic injection definitively failed: the render bundle shares the same
|
|
907
|
+
// (unreachable) resource tree, so `loadResource` here would only produce a
|
|
908
|
+
// second cryptic `module <pagePath> not found`. bootServiceHost already
|
|
909
|
+
// surfaced the actionable diagnostic — stay silent.
|
|
910
|
+
if (ap.logicInjected === false)
|
|
911
|
+
return;
|
|
912
|
+
// Injection still in-flight: the render guest fires `renderHostReady` on its
|
|
913
|
+
// own DOMContentLoaded, which routinely beats the async fetch+inject. Hold
|
|
914
|
+
// the render `loadResource` and let `bootServiceHost` flush it once the
|
|
915
|
+
// bundle settles, so a failed bundle never reaches the render side.
|
|
916
|
+
if (ap.logicInjected === null) {
|
|
917
|
+
page.renderLoadPending = true;
|
|
918
|
+
return;
|
|
847
919
|
}
|
|
920
|
+
sendRenderLoadResource(ap, page);
|
|
848
921
|
return;
|
|
849
922
|
}
|
|
850
923
|
if (msg.type === 'renderResourceLoaded') {
|
|
@@ -882,9 +955,20 @@ function handleContainerMsg(ap, page, msg, ctx, state) {
|
|
|
882
955
|
case 'invokeAPI':
|
|
883
956
|
void handleSimulatorApi(state, ap, page, msg.body, ctx);
|
|
884
957
|
break;
|
|
885
|
-
case 'serviceHostError':
|
|
958
|
+
case 'serviceHostError': {
|
|
886
959
|
console.warn('[bridge-router] service host error:', msg.body);
|
|
960
|
+
// Forward the error to every registered `wx.onError` listener. (The dimina
|
|
961
|
+
// service runtime's App lifecycle is only onLaunch/onShow/onHide, so it
|
|
962
|
+
// does not dispatch `App.onError` — `wx.onError` is the supported path.)
|
|
963
|
+
// The service preload's `deliver` try/catch already reports errors thrown
|
|
964
|
+
// during lifecycle/event dispatch here.
|
|
965
|
+
const errBody = msg.body;
|
|
966
|
+
const errArg = errBody?.message ?? msg.body;
|
|
967
|
+
for (const id of state.appLifecycle.listeners(ap.appSessionId, 'onError')) {
|
|
968
|
+
sendCallback(ap, id, errArg);
|
|
969
|
+
}
|
|
887
970
|
break;
|
|
971
|
+
}
|
|
888
972
|
case 'consoleLog':
|
|
889
973
|
// Native-host console capture: the render-host / service-host guest preloads
|
|
890
974
|
// monkeypatch console.* and post each entry here (one case handles both —
|
|
@@ -922,6 +1006,54 @@ const TAB_ACTION_NAMES = new Set([
|
|
|
922
1006
|
'showTabBarRedDot',
|
|
923
1007
|
'hideTabBarRedDot',
|
|
924
1008
|
]);
|
|
1009
|
+
/**
|
|
1010
|
+
* Drive app foreground/background from the main window's visibility. Minimizing
|
|
1011
|
+
* or hiding the window backgrounds the mini-program (App.onHide + wx.onAppHide);
|
|
1012
|
+
* restoring or showing it foregrounds it (App.onShow + wx.onAppShow). Focus/blur
|
|
1013
|
+
* is deliberately NOT used — clicking a DevTools panel would falsely fire it.
|
|
1014
|
+
* The initial App.onShow fires once at spawn (service `invokeSomeLifecycle`), so
|
|
1015
|
+
* the driver only handles subsequent transitions.
|
|
1016
|
+
*/
|
|
1017
|
+
function installAppLifecycleDriver(ctx, state) {
|
|
1018
|
+
const win = ctx.windows?.mainWindow;
|
|
1019
|
+
// Guard against a headless/stub mainWindow (unit tests) that isn't a real
|
|
1020
|
+
// event-emitting BrowserWindow.
|
|
1021
|
+
if (!win || typeof win.on !== 'function')
|
|
1022
|
+
return;
|
|
1023
|
+
const emit = (serviceEvent, listenerEvent) => {
|
|
1024
|
+
for (const ap of state.appSessions.values()) {
|
|
1025
|
+
// App.onShow / onHide: the service runtime already listens for these.
|
|
1026
|
+
forwardToService(ap, { type: serviceEvent, target: 'service', body: {} });
|
|
1027
|
+
// wx.onAppShow / onAppHide imperative listeners.
|
|
1028
|
+
for (const id of state.appLifecycle.listeners(ap.appSessionId, listenerEvent)) {
|
|
1029
|
+
sendCallback(ap, id, {});
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
};
|
|
1033
|
+
const onHide = () => emit('appHide', 'onAppHide');
|
|
1034
|
+
const onShow = () => emit('appShow', 'onAppShow');
|
|
1035
|
+
win.on('hide', onHide);
|
|
1036
|
+
win.on('minimize', onHide);
|
|
1037
|
+
win.on('show', onShow);
|
|
1038
|
+
win.on('restore', onShow);
|
|
1039
|
+
ctx.registry.add(() => {
|
|
1040
|
+
win.off('hide', onHide);
|
|
1041
|
+
win.off('minimize', onHide);
|
|
1042
|
+
win.off('show', onShow);
|
|
1043
|
+
win.off('restore', onShow);
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
// wx app-event API name → the lifecycle event whose listeners it (de)registers.
|
|
1047
|
+
const APP_LIFECYCLE_REGISTER = {
|
|
1048
|
+
onAppShow: 'onAppShow',
|
|
1049
|
+
onAppHide: 'onAppHide',
|
|
1050
|
+
onError: 'onError',
|
|
1051
|
+
};
|
|
1052
|
+
const APP_LIFECYCLE_UNREGISTER = {
|
|
1053
|
+
offAppShow: 'onAppShow',
|
|
1054
|
+
offAppHide: 'onAppHide',
|
|
1055
|
+
offError: 'onError',
|
|
1056
|
+
};
|
|
925
1057
|
async function handleSimulatorApi(state, ap, page, body, ctx) {
|
|
926
1058
|
const name = String(body.name ?? '');
|
|
927
1059
|
const params = normalizeParams(body.params);
|
|
@@ -974,6 +1106,36 @@ async function handleSimulatorApi(state, ap, page, body, ctx) {
|
|
|
974
1106
|
}
|
|
975
1107
|
return;
|
|
976
1108
|
}
|
|
1109
|
+
// App-level lifecycle listeners (wx.onAppShow / onAppHide / onError + off*).
|
|
1110
|
+
// The service encodes the listener as a keep callback id in `params.success`;
|
|
1111
|
+
// we store it and re-fire on main-window foreground/background (the driver in
|
|
1112
|
+
// installBridgeRouter) and on serviceHostError. `off*` clears the event's
|
|
1113
|
+
// listeners for the session.
|
|
1114
|
+
const lifecycleRegister = APP_LIFECYCLE_REGISTER[name];
|
|
1115
|
+
if (lifecycleRegister) {
|
|
1116
|
+
state.appLifecycle.register(ap.appSessionId, lifecycleRegister, params.success);
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
const lifecycleUnregister = APP_LIFECYCLE_UNREGISTER[name];
|
|
1120
|
+
if (lifecycleUnregister) {
|
|
1121
|
+
// off(cb) carries the same evtId as the original on(cb) → removes just that
|
|
1122
|
+
// listener; off() with no callback carries no id → clears the whole event.
|
|
1123
|
+
state.appLifecycle.unregister(ap.appSessionId, lifecycleUnregister, params.success);
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
// pageScrollTo acts on the page's render guest (scroll its document), which
|
|
1127
|
+
// only the main process can reach — run the scroll script in the invoking
|
|
1128
|
+
// page's render webContents rather than forwarding to the simulator.
|
|
1129
|
+
if (name === 'pageScrollTo') {
|
|
1130
|
+
const renderWc = page.renderWc;
|
|
1131
|
+
if (renderWc && !renderWc.isDestroyed()) {
|
|
1132
|
+
void renderWc.executeJavaScript(buildPageScrollScript(params)).catch(() => { });
|
|
1133
|
+
}
|
|
1134
|
+
const successResult = { errMsg: 'pageScrollTo:ok' };
|
|
1135
|
+
sendCallback(ap, params.success, successResult);
|
|
1136
|
+
sendCallback(ap, params.complete, successResult);
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
977
1139
|
// Native-host storage unification: route async wx.setStorage/getStorage/etc.
|
|
978
1140
|
// to the service-host window's file:// store (the same store the *Sync APIs +
|
|
979
1141
|
// the Storage panel use), instead of forwarding to the simulator guest's
|
|
@@ -1266,6 +1428,7 @@ async function disposeAppSession(state, appSessionId, opts = {}) {
|
|
|
1266
1428
|
if (!ap)
|
|
1267
1429
|
return;
|
|
1268
1430
|
state.appSessions.delete(appSessionId);
|
|
1431
|
+
state.appLifecycle.dispose(appSessionId);
|
|
1269
1432
|
// Evict AppData bridges FIRST — eviction enumerates `ap.pages`, which the
|
|
1270
1433
|
// page teardown below progressively empties (and finally clears).
|
|
1271
1434
|
state.evictAppDataBridges(ap);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the script that `wx.pageScrollTo` runs inside the page's render guest.
|
|
3
|
+
*
|
|
4
|
+
* The mini-program page scrolls the whole render document (the native iOS impl
|
|
5
|
+
* scrolls the WKWebView's scrollView; Android scrolls the page view), so the
|
|
6
|
+
* simulator scrolls `window`. `duration` is best-effort: a positive duration
|
|
7
|
+
* maps to smooth scrolling (the browser owns the easing — the exact ms is not
|
|
8
|
+
* controllable), and `0` jumps instantly.
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildPageScrollScript(params: {
|
|
11
|
+
scrollTop?: unknown;
|
|
12
|
+
duration?: unknown;
|
|
13
|
+
}): string;
|
|
14
|
+
//# sourceMappingURL=page-scroll.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the script that `wx.pageScrollTo` runs inside the page's render guest.
|
|
3
|
+
*
|
|
4
|
+
* The mini-program page scrolls the whole render document (the native iOS impl
|
|
5
|
+
* scrolls the WKWebView's scrollView; Android scrolls the page view), so the
|
|
6
|
+
* simulator scrolls `window`. `duration` is best-effort: a positive duration
|
|
7
|
+
* maps to smooth scrolling (the browser owns the easing — the exact ms is not
|
|
8
|
+
* controllable), and `0` jumps instantly.
|
|
9
|
+
*/
|
|
10
|
+
export function buildPageScrollScript(params) {
|
|
11
|
+
const rawTop = Number(params.scrollTop);
|
|
12
|
+
const top = Number.isFinite(rawTop) ? rawTop : 0;
|
|
13
|
+
const duration = params.duration === undefined ? 300 : Number(params.duration);
|
|
14
|
+
const behavior = Number.isFinite(duration) && duration > 0 ? 'smooth' : 'auto';
|
|
15
|
+
return `window.scrollTo({ top: ${top}, left: 0, behavior: ${JSON.stringify(behavior)} })`;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=page-scroll.js.map
|
|
@@ -21,6 +21,13 @@ export interface ServiceHostWindowOptions {
|
|
|
21
21
|
root?: string;
|
|
22
22
|
resourceBaseUrl: string;
|
|
23
23
|
hostEnvSnapshot?: Record<string, unknown>;
|
|
24
|
+
/**
|
|
25
|
+
* Custom API namespace names (host config `apiNamespaces`, e.g. `['qd']`).
|
|
26
|
+
* Encoded into the spawn URL so the service-host preload can install them as
|
|
27
|
+
* `globalThis.__diminaApiNamespaces` before service.js evaluates — without
|
|
28
|
+
* which page logic referencing `qd.*` throws `ReferenceError: qd is not defined`.
|
|
29
|
+
*/
|
|
30
|
+
apiNamespaces?: string[];
|
|
24
31
|
}
|
|
25
32
|
/**
|
|
26
33
|
* Construct an (un-navigated) service-host BrowserWindow. Split out from
|
|
@@ -60,6 +60,11 @@ export function buildServiceHostSpawnUrl(opts) {
|
|
|
60
60
|
// Without this, sync-impls/system-info.ts falls back to generic defaults.
|
|
61
61
|
url.searchParams.set('hostEnv', encodeURIComponent(JSON.stringify(opts.hostEnvSnapshot)));
|
|
62
62
|
}
|
|
63
|
+
if (opts.apiNamespaces && opts.apiNamespaces.length > 0) {
|
|
64
|
+
// CSV of namespace names; the preload reads this back into
|
|
65
|
+
// `globalThis.__diminaApiNamespaces`. URLSearchParams round-trips the comma.
|
|
66
|
+
url.searchParams.set('apiNamespaces', opts.apiNamespaces.join(','));
|
|
67
|
+
}
|
|
63
68
|
return url.toString();
|
|
64
69
|
}
|
|
65
70
|
/**
|
|
@@ -327,6 +327,68 @@ function vibrateLong({ success, complete } = {}) {
|
|
|
327
327
|
onComplete?.();
|
|
328
328
|
}
|
|
329
329
|
var scanCode = notSupportedApi("scanCode");
|
|
330
|
+
function getClipboardData({ success, fail, complete } = {}) {
|
|
331
|
+
const { onSuccess, onFail, onComplete } = bindCallbacks(this, { success, fail, complete });
|
|
332
|
+
return navigator.clipboard.readText().then(
|
|
333
|
+
(data) => {
|
|
334
|
+
onSuccess?.({ data, errMsg: "getClipboardData:ok" });
|
|
335
|
+
onComplete?.();
|
|
336
|
+
},
|
|
337
|
+
(error) => {
|
|
338
|
+
onFail?.({ errMsg: `getClipboardData:fail ${error?.message ?? error}` });
|
|
339
|
+
onComplete?.();
|
|
340
|
+
}
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
function setClipboardData({ data, success, fail, complete } = {}) {
|
|
344
|
+
const { onSuccess, onFail, onComplete } = bindCallbacks(this, { success, fail, complete });
|
|
345
|
+
if (typeof data !== "string") {
|
|
346
|
+
onFail?.({ errMsg: "setClipboardData:fail data is required" });
|
|
347
|
+
onComplete?.();
|
|
348
|
+
return Promise.resolve();
|
|
349
|
+
}
|
|
350
|
+
return navigator.clipboard.writeText(data).then(
|
|
351
|
+
() => {
|
|
352
|
+
onSuccess?.({ errMsg: "setClipboardData:ok" });
|
|
353
|
+
onComplete?.();
|
|
354
|
+
},
|
|
355
|
+
(error) => {
|
|
356
|
+
onFail?.({ errMsg: `setClipboardData:fail ${error?.message ?? error}` });
|
|
357
|
+
onComplete?.();
|
|
358
|
+
}
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
function mapEffectiveType(effectiveType) {
|
|
362
|
+
switch (effectiveType) {
|
|
363
|
+
case "4g":
|
|
364
|
+
return "4g";
|
|
365
|
+
case "3g":
|
|
366
|
+
return "3g";
|
|
367
|
+
case "2g":
|
|
368
|
+
case "slow-2g":
|
|
369
|
+
return "2g";
|
|
370
|
+
default:
|
|
371
|
+
return "unknown";
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
function resolveNetworkType(info) {
|
|
375
|
+
if (!info.onLine) return "none";
|
|
376
|
+
if (info.type === "wifi" || info.type === "ethernet") return "wifi";
|
|
377
|
+
if (info.type === "cellular") return mapEffectiveType(info.effectiveType);
|
|
378
|
+
if (info.effectiveType) return mapEffectiveType(info.effectiveType);
|
|
379
|
+
return "wifi";
|
|
380
|
+
}
|
|
381
|
+
function getNetworkType({ success, complete } = {}) {
|
|
382
|
+
const { onSuccess, onComplete } = bindCallbacks(this, { success, complete });
|
|
383
|
+
const connection = navigator.connection;
|
|
384
|
+
const networkType = resolveNetworkType({
|
|
385
|
+
onLine: navigator.onLine,
|
|
386
|
+
type: connection?.type,
|
|
387
|
+
effectiveType: connection?.effectiveType
|
|
388
|
+
});
|
|
389
|
+
onSuccess?.({ networkType, errMsg: "getNetworkType:ok" });
|
|
390
|
+
onComplete?.();
|
|
391
|
+
}
|
|
330
392
|
|
|
331
393
|
// src/simulator/event-bridge.ts
|
|
332
394
|
function bindDomEvents(target, eventMap, fire, buildPayload) {
|
|
@@ -1754,6 +1816,134 @@ function uploadFileAbort({ uploadId } = {}) {
|
|
|
1754
1816
|
}
|
|
1755
1817
|
}
|
|
1756
1818
|
|
|
1819
|
+
// src/simulator/ui-overlay-bus.ts
|
|
1820
|
+
var UiOverlayBus = class {
|
|
1821
|
+
state = { toast: null, dialog: null };
|
|
1822
|
+
listeners = /* @__PURE__ */ new Set();
|
|
1823
|
+
getState() {
|
|
1824
|
+
return this.state;
|
|
1825
|
+
}
|
|
1826
|
+
subscribe(listener) {
|
|
1827
|
+
this.listeners.add(listener);
|
|
1828
|
+
return () => {
|
|
1829
|
+
this.listeners.delete(listener);
|
|
1830
|
+
};
|
|
1831
|
+
}
|
|
1832
|
+
showToast(toast) {
|
|
1833
|
+
this.set({ ...this.state, toast });
|
|
1834
|
+
}
|
|
1835
|
+
hideToast() {
|
|
1836
|
+
this.set({ ...this.state, toast: null });
|
|
1837
|
+
}
|
|
1838
|
+
/**
|
|
1839
|
+
* Clear `toast` only if it is still the active one. The toast auto-dismiss
|
|
1840
|
+
* timer holds a reference to the toast it scheduled; a newer showToast may
|
|
1841
|
+
* have replaced it in the meantime, and a blind `hideToast()` from the stale
|
|
1842
|
+
* timer would wrongly clear the new toast.
|
|
1843
|
+
*/
|
|
1844
|
+
dismissToast(toast) {
|
|
1845
|
+
if (this.state.toast === toast) this.hideToast();
|
|
1846
|
+
}
|
|
1847
|
+
showDialog(dialog) {
|
|
1848
|
+
this.set({ ...this.state, dialog });
|
|
1849
|
+
}
|
|
1850
|
+
hideDialog() {
|
|
1851
|
+
this.set({ ...this.state, dialog: null });
|
|
1852
|
+
}
|
|
1853
|
+
set(next) {
|
|
1854
|
+
this.state = next;
|
|
1855
|
+
for (const listener of this.listeners) listener(next);
|
|
1856
|
+
}
|
|
1857
|
+
};
|
|
1858
|
+
var uiOverlayBus = new UiOverlayBus();
|
|
1859
|
+
|
|
1860
|
+
// src/simulator/simulator-api-ui.ts
|
|
1861
|
+
function showToast(opts = {}) {
|
|
1862
|
+
const { onSuccess, onComplete } = bindCallbacks(this, opts);
|
|
1863
|
+
uiOverlayBus.showToast({
|
|
1864
|
+
title: opts.title ?? "",
|
|
1865
|
+
icon: opts.icon ?? "success",
|
|
1866
|
+
image: opts.image,
|
|
1867
|
+
duration: opts.duration ?? 1500,
|
|
1868
|
+
mask: opts.mask ?? false
|
|
1869
|
+
});
|
|
1870
|
+
onSuccess?.({ errMsg: "showToast:ok" });
|
|
1871
|
+
onComplete?.();
|
|
1872
|
+
}
|
|
1873
|
+
function hideToast(opts = {}) {
|
|
1874
|
+
const { onSuccess, onComplete } = bindCallbacks(this, opts);
|
|
1875
|
+
uiOverlayBus.hideToast();
|
|
1876
|
+
onSuccess?.({ errMsg: "hideToast:ok" });
|
|
1877
|
+
onComplete?.();
|
|
1878
|
+
}
|
|
1879
|
+
function showLoading(opts = {}) {
|
|
1880
|
+
const { onSuccess, onComplete } = bindCallbacks(this, opts);
|
|
1881
|
+
uiOverlayBus.showToast({
|
|
1882
|
+
title: opts.title ?? "",
|
|
1883
|
+
icon: "loading",
|
|
1884
|
+
duration: Infinity,
|
|
1885
|
+
mask: opts.mask ?? false
|
|
1886
|
+
});
|
|
1887
|
+
onSuccess?.({ errMsg: "showLoading:ok" });
|
|
1888
|
+
onComplete?.();
|
|
1889
|
+
}
|
|
1890
|
+
function hideLoading(opts = {}) {
|
|
1891
|
+
const { onSuccess, onComplete } = bindCallbacks(this, opts);
|
|
1892
|
+
uiOverlayBus.hideToast();
|
|
1893
|
+
onSuccess?.({ errMsg: "hideLoading:ok" });
|
|
1894
|
+
onComplete?.();
|
|
1895
|
+
}
|
|
1896
|
+
function showModal(opts = {}) {
|
|
1897
|
+
const { onSuccess, onComplete } = bindCallbacks(this, opts);
|
|
1898
|
+
const editable = opts.editable ?? false;
|
|
1899
|
+
let settled = false;
|
|
1900
|
+
uiOverlayBus.showDialog({
|
|
1901
|
+
kind: "modal",
|
|
1902
|
+
title: opts.title ?? "",
|
|
1903
|
+
content: opts.content ?? "",
|
|
1904
|
+
showCancel: opts.showCancel ?? true,
|
|
1905
|
+
cancelText: opts.cancelText ?? "\u53D6\u6D88",
|
|
1906
|
+
cancelColor: opts.cancelColor ?? "#000000",
|
|
1907
|
+
confirmText: opts.confirmText ?? "\u786E\u5B9A",
|
|
1908
|
+
confirmColor: opts.confirmColor ?? "#576B95",
|
|
1909
|
+
editable,
|
|
1910
|
+
placeholderText: opts.placeholderText ?? "",
|
|
1911
|
+
onResult: (confirmed, content) => {
|
|
1912
|
+
if (settled) return;
|
|
1913
|
+
settled = true;
|
|
1914
|
+
uiOverlayBus.hideDialog();
|
|
1915
|
+
const result = {
|
|
1916
|
+
confirm: confirmed,
|
|
1917
|
+
cancel: !confirmed,
|
|
1918
|
+
errMsg: "showModal:ok"
|
|
1919
|
+
};
|
|
1920
|
+
if (editable) result.content = content ?? "";
|
|
1921
|
+
onSuccess?.(result);
|
|
1922
|
+
onComplete?.();
|
|
1923
|
+
}
|
|
1924
|
+
});
|
|
1925
|
+
}
|
|
1926
|
+
function showActionSheet(opts = {}) {
|
|
1927
|
+
const { onSuccess, onFail, onComplete } = bindCallbacks(this, opts);
|
|
1928
|
+
let settled = false;
|
|
1929
|
+
uiOverlayBus.showDialog({
|
|
1930
|
+
kind: "actionSheet",
|
|
1931
|
+
itemList: opts.itemList ?? [],
|
|
1932
|
+
itemColor: opts.itemColor ?? "#000000",
|
|
1933
|
+
onSelect: (index) => {
|
|
1934
|
+
if (settled) return;
|
|
1935
|
+
settled = true;
|
|
1936
|
+
uiOverlayBus.hideDialog();
|
|
1937
|
+
if (index === -1) {
|
|
1938
|
+
onFail?.({ errMsg: "showActionSheet:fail cancel" });
|
|
1939
|
+
} else {
|
|
1940
|
+
onSuccess?.({ tapIndex: index, errMsg: "showActionSheet:ok" });
|
|
1941
|
+
}
|
|
1942
|
+
onComplete?.();
|
|
1943
|
+
}
|
|
1944
|
+
});
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1757
1947
|
// src/simulator/simulator-api.ts
|
|
1758
1948
|
function canIUse({ success, complete }) {
|
|
1759
1949
|
const { onSuccess, onComplete } = bindCallbacks(this, { success, complete });
|
|
@@ -1872,6 +2062,13 @@ var simulatorApis = {
|
|
|
1872
2062
|
getSystemInfoSync,
|
|
1873
2063
|
getWindowInfo,
|
|
1874
2064
|
getSystemSetting,
|
|
2065
|
+
// UI: interaction
|
|
2066
|
+
showToast,
|
|
2067
|
+
hideToast,
|
|
2068
|
+
showLoading,
|
|
2069
|
+
hideLoading,
|
|
2070
|
+
showModal,
|
|
2071
|
+
showActionSheet,
|
|
1875
2072
|
// Network
|
|
1876
2073
|
downloadFile,
|
|
1877
2074
|
uploadFile,
|
|
@@ -1899,6 +2096,9 @@ var simulatorApis = {
|
|
|
1899
2096
|
vibrateShort,
|
|
1900
2097
|
vibrateLong,
|
|
1901
2098
|
scanCode,
|
|
2099
|
+
getClipboardData,
|
|
2100
|
+
setClipboardData,
|
|
2101
|
+
getNetworkType,
|
|
1902
2102
|
// Media: Image
|
|
1903
2103
|
chooseImage,
|
|
1904
2104
|
previewImage,
|