peachsim 0.1.577 → 0.1.579
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/LICENSE +1 -1
- package/README.md +11 -9
- package/cli/app-catalog.ts +77 -0
- package/cli/app-fonts.ts +4 -20
- package/cli/app-splash.ts +2 -18
- package/cli/bridge-flow-runner.ts +505 -139
- package/cli/cloud-client.ts +1 -4
- package/cli/cloud-session.ts +1 -4
- package/cli/cloud-test-result-files.ts +3 -0
- package/cli/cloud-test-tunnel.ts +2 -5
- package/cli/command-registry.ts +2 -0
- package/cli/commands/agent.ts +149 -23
- package/cli/commands/assert.ts +1 -4
- package/cli/commands/box/peach-command.ts +62 -0
- package/cli/commands/box/plane-share.ts +0 -70
- package/cli/commands/box/rnx-command.ts +1 -60
- package/cli/commands/box/search-command.ts +14 -0
- package/cli/commands/box.ts +2 -2
- package/cli/commands/control.ts +121 -27
- package/cli/commands/cpu-profile.ts +23 -10
- package/cli/commands/daemon.ts +1 -9
- package/cli/commands/detox.ts +26 -18
- package/cli/commands/device.ts +129 -1
- package/cli/commands/film.ts +25 -16
- package/cli/commands/flow.ts +9 -2
- package/cli/commands/inspect/actions.ts +75 -1
- package/cli/commands/inspect/core.ts +81 -2
- package/cli/commands/inspect/describe.ts +2 -6
- package/cli/commands/inspect/env.ts +1 -1
- package/cli/commands/inspect/settling.ts +20 -3
- package/cli/commands/inspect/shared.ts +1 -4
- package/cli/commands/inspect/sleep.ts +1 -1
- package/cli/commands/inspect.ts +16 -49
- package/cli/commands/install-desktop.ts +8 -6
- package/cli/commands/login.ts +3 -5
- package/cli/commands/maestro-generate.ts +1 -4
- package/cli/commands/maestro.ts +38 -21
- package/cli/commands/no-bridge-hint.ts +0 -14
- package/cli/commands/permissions.ts +2 -2
- package/cli/commands/platform.ts +13 -6
- package/cli/commands/screenshot.ts +6 -1
- package/cli/commands/skills.ts +1 -1
- package/cli/commands/test.ts +6 -2
- package/cli/commands/three-mode.ts +162 -19
- package/cli/commands/upload.ts +3 -18
- package/cli/current-sim.ts +0 -1
- package/cli/desktop-companion.ts +40 -8
- package/cli/drivers/playwright-provisioning.ts +1 -4
- package/cli/flow-export.ts +1 -8
- package/cli/flow-session.ts +0 -4
- package/cli/hints.ts +1 -16
- package/cli/inspect-notice-state.ts +0 -4
- package/cli/main.ts +2 -4
- package/cli/outbound-endpoints.ts +1 -24
- package/cli/parse-args.ts +1 -2
- package/cli/rnx-in-process.ts +3 -1
- package/cli/run-rnx-core.ts +4 -2
- package/cli/run-rnx.ts +18 -10
- package/cli/send-to-box.ts +1 -4
- package/cli/telemetry.ts +1 -1
- package/cli/ws-bridge.ts +1 -5
- package/detox/gestures.ts +20 -6
- package/detox/index.ts +379 -123
- package/detox/navigation-lifecycle.ts +22 -13
- package/dist-lib/agent-daemon-client.cjs +21 -15
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-identity.cjs +3 -17
- package/dist-lib/agent-sessions.cjs +5 -4
- package/dist-lib/attached-projects.cjs +1 -77
- package/dist-lib/auth/shared-session.cjs +1 -1
- package/dist-lib/backend-origin.cjs +11 -1
- package/dist-lib/beta.cjs +1 -1
- package/dist-lib/beta.mjs +1 -1
- package/dist-lib/bridge-constants.cjs +1 -1
- package/dist-lib/bridge-contract-input.cjs +7 -5
- package/dist-lib/bridge-contract-input.mjs +7 -5
- package/dist-lib/bridge-contract.cjs +1 -1
- package/dist-lib/bridge-contract.mjs +1 -1
- package/dist-lib/capture-contract.cjs +30 -7
- package/dist-lib/capture-contract.mjs +30 -7
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/cloud-contract.cjs +1 -1
- package/dist-lib/cloud-contract.mjs +1 -1
- package/dist-lib/cloud-test-args.cjs +1 -1
- package/dist-lib/cloud-test-args.mjs +1 -1
- package/dist-lib/cloud.cjs +17 -11
- package/dist-lib/cloud.mjs +17 -11
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +321 -122
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +427 -212
- package/dist-lib/host/fetch-proxy-handler.cjs +5 -28
- package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
- package/dist-lib/host/replacement-module-handler.cjs +1 -1
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +315 -58
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/jump-to-source-native.cjs +1 -1
- package/dist-lib/menu.cjs +67 -31
- package/dist-lib/menu.mjs +64 -28
- package/dist-lib/metro-fingerprint-registry.cjs +8 -4
- package/dist-lib/metro-fingerprint-registry.mjs +6 -4
- package/dist-lib/metro-production-bundle.cjs +2 -2
- package/dist-lib/metro-production-bundle.mjs +2 -2
- package/dist-lib/metro.cjs +8 -4
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +7 -4
- package/dist-lib/react-native-host-modules.cjs +1 -1
- package/dist-lib/react-native-host-modules.mjs +1 -1
- package/dist-lib/render-mode.cjs +3 -13
- package/dist-lib/scripts/dev-server-scanner.cjs +5 -17
- package/dist-lib/sdk.cjs +129 -10
- package/dist-lib/sdk.mjs +127 -10
- package/dist-lib/skills.cjs +1635 -1121
- package/dist-lib/swift-compile.cjs +277 -70
- package/dist-lib/swift-compile.mjs +275 -70
- package/dist-lib/swift-package.cjs +17 -5
- package/dist-lib/swift-package.mjs +15 -4
- package/dist-lib/swift-submit.cjs +32 -8
- package/dist-lib/swift-submit.mjs +29 -7
- package/dist-lib/swift.cjs +30912 -577
- package/dist-lib/vite-kotlin.cjs +356 -0
- package/dist-lib/vite-swift.cjs +1743 -0
- package/dist-lib/vite.cjs +208 -1039
- package/kotlin/README.md +18 -0
- package/kotlin/Sources/androidx/annotation/Keep.kt +4 -0
- package/kotlin/Sources/dev/onejs/one/OneNativeSourceDispatch.kt +7 -0
- package/kotlin/Sources/dev/onejs/one/source/OneModule.kt +4 -0
- package/kotlin/Sources/org/json/JSON.kt +244 -0
- package/kotlin/toolchain.json +19 -0
- package/package.json +14 -2
- package/scripts/dev-server-scanner.ts +0 -18
- package/scripts/optional-demo-registry.ts +0 -17
- package/skills/contrast/SKILL.md +2 -2
- package/skills/peach-setup/SKILL.md +16 -0
- package/src/agent-daemon-client.ts +36 -22
- package/src/agent-identity.ts +0 -15
- package/src/agent-prompt.ts +12 -0
- package/src/attached-projects.ts +0 -72
- package/src/backend-origin.ts +10 -0
- package/src/bridge-contract-input.ts +2 -5
- package/src/bridge-contract.ts +2 -0
- package/src/capture-contract.ts +53 -11
- package/src/cloud.ts +4 -9
- package/src/dev-server-open.ts +0 -99
- package/src/home-paths.ts +2 -3
- package/src/host/agent-host.ts +82 -36
- package/src/host/bridge-host.ts +5 -11
- package/src/host/fetch-proxy-handler.ts +19 -43
- package/src/is-record.ts +4 -0
- package/src/menu.ts +82 -37
- package/src/metro-fingerprint-registry.ts +1 -4
- package/src/metro-production-bundle.ts +1 -1
- package/src/plist.ts +1 -4
- package/src/public-brand.ts +5 -3
- package/src/render-mode.ts +0 -12
- package/src/runtime-assets.ts +9 -2
- package/src/screenshots/compose.ts +7 -1
- package/src/screenshots/frame-compose.ts +21 -10
- package/src/screenshots/schema.ts +1 -4
- package/src/sim-client.ts +2 -4
- package/src/skills/builtin/maestro-test.ts +1 -4
- package/src/sleep.ts +4 -0
- package/src/swift-compile.ts +209 -47
- package/src/swift-icu.ts +140 -0
- package/src/swift-package.ts +26 -2
- package/src/swift-submit.ts +84 -4
- package/src/swift.ts +2 -0
- package/src/vite-plugin-kotlin.ts +376 -0
- package/src/vite-plugin-one.ts +224 -36
- package/src/vite-plugin-swift.ts +382 -96
- package/src/vite-plugin.ts +121 -134
- package/src/vite.d.ts +10 -0
- package/cli/resolve-assets.ts +0 -63
- package/src/rolldown-plugin-swift.ts +0 -244
- package/src/team-machine-prompt.ts +0 -73
package/dist-lib/detox/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.579 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __create = Object.create;
|
|
@@ -11,8 +11,8 @@ var __commonJS = (cb, mod) => function __require() {
|
|
|
11
11
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
12
12
|
};
|
|
13
13
|
var __export = (target, all) => {
|
|
14
|
-
for (var
|
|
15
|
-
__defProp(target,
|
|
14
|
+
for (var name2 in all)
|
|
15
|
+
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
16
16
|
};
|
|
17
17
|
var __copyProps = (to, from, except, desc) => {
|
|
18
18
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
@@ -301,13 +301,13 @@ function assertGpuBrowserCommand(command, runtime = currentRuntime()) {
|
|
|
301
301
|
`[reap-browser] Chrome process did not retain the required ${angle} GPU arguments`
|
|
302
302
|
);
|
|
303
303
|
}
|
|
304
|
-
for (const
|
|
305
|
-
const lists = args.filter((arg) => arg.startsWith(
|
|
304
|
+
for (const name2 of ["--enable-features=", "--disable-features="]) {
|
|
305
|
+
const lists = args.filter((arg) => arg.startsWith(name2)).map((arg) => arg.slice(name2.length).split(",").filter(Boolean));
|
|
306
306
|
const kept = new Set(lists.at(-1) ?? []);
|
|
307
307
|
const dropped = lists.slice(0, -1).flat().filter((feature) => !kept.has(feature));
|
|
308
308
|
if (dropped.length > 0) {
|
|
309
309
|
throw new Error(
|
|
310
|
-
`[reap-browser] Chrome keeps only its last ${
|
|
310
|
+
`[reap-browser] Chrome keeps only its last ${name2.slice(0, -1)} switch, which dropped ${dropped.join(",")}; add them to the merged list in scripts/lib/reap-browser.ts`
|
|
311
311
|
);
|
|
312
312
|
}
|
|
313
313
|
}
|
|
@@ -884,6 +884,19 @@ function isAppBootTargetUrl(initUrl) {
|
|
|
884
884
|
}
|
|
885
885
|
}
|
|
886
886
|
|
|
887
|
+
// src/public-brand.ts
|
|
888
|
+
var name = "Peach";
|
|
889
|
+
var peachPublicBrand = Object.freeze({
|
|
890
|
+
name,
|
|
891
|
+
packageName: "peachsim",
|
|
892
|
+
commandName: "peach",
|
|
893
|
+
domain: "peachsim.com",
|
|
894
|
+
origin: "https://peachsim.com"
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
// src/menu.ts
|
|
898
|
+
var SOOTSIM_SHELL_COMMAND_NAME = "sootsim:shell-command";
|
|
899
|
+
|
|
887
900
|
// src/poll-until.ts
|
|
888
901
|
async function pollUntil(read, ready, options) {
|
|
889
902
|
if (!Number.isFinite(options.timeoutMs) || options.timeoutMs <= 0) {
|
|
@@ -979,7 +992,14 @@ async function pollUntil(read, ready, options) {
|
|
|
979
992
|
}
|
|
980
993
|
}
|
|
981
994
|
|
|
995
|
+
// ../peach-engine/src/engine/scroll-platform.ts
|
|
996
|
+
var IOS_SCROLL_AXIS_TANGENT = Math.sqrt(3);
|
|
997
|
+
function scrollTouchSlop(platform, primaryDelta) {
|
|
998
|
+
return platform === "android" ? 0 : Math.sign(primaryDelta) * 10;
|
|
999
|
+
}
|
|
1000
|
+
|
|
982
1001
|
// detox/gestures.ts
|
|
1002
|
+
var DETOX_PLATFORM = process.env.PEACH_PLATFORM === "android" ? "android" : "ios";
|
|
983
1003
|
function resolveScrollStartPosition(value, label) {
|
|
984
1004
|
const normalized = value === void 0 || Number.isNaN(value) ? 0.5 : value;
|
|
985
1005
|
if (!Number.isFinite(normalized) || normalized < 0 || normalized > 1) {
|
|
@@ -1171,23 +1191,24 @@ async function dragScrollNode(page, node, pixels, direction, startPositionX, sta
|
|
|
1171
1191
|
const startSootSimY = node.absolutePosition.y + node.layout.height * normalizedStartY;
|
|
1172
1192
|
let endSootSimX = startSootSimX;
|
|
1173
1193
|
let endSootSimY = startSootSimY;
|
|
1194
|
+
const travel = pixels + scrollTouchSlop(DETOX_PLATFORM, 1);
|
|
1174
1195
|
switch (direction) {
|
|
1175
1196
|
case "down":
|
|
1176
|
-
endSootSimY -=
|
|
1197
|
+
endSootSimY -= travel;
|
|
1177
1198
|
break;
|
|
1178
1199
|
case "up":
|
|
1179
|
-
endSootSimY +=
|
|
1200
|
+
endSootSimY += travel;
|
|
1180
1201
|
break;
|
|
1181
1202
|
case "left":
|
|
1182
|
-
endSootSimX +=
|
|
1203
|
+
endSootSimX += travel;
|
|
1183
1204
|
break;
|
|
1184
1205
|
case "right":
|
|
1185
|
-
endSootSimX -=
|
|
1206
|
+
endSootSimX -= travel;
|
|
1186
1207
|
break;
|
|
1187
1208
|
}
|
|
1188
1209
|
const startPage = await sootsimToPage(page, startSootSimX, startSootSimY);
|
|
1189
1210
|
const endPage = await sootsimToPage(page, endSootSimX, endSootSimY);
|
|
1190
|
-
const steps = Math.max(16, Math.ceil(
|
|
1211
|
+
const steps = Math.max(16, Math.ceil(travel / 8));
|
|
1191
1212
|
await page.mouse.move(startPage.x, startPage.y);
|
|
1192
1213
|
await page.mouse.down();
|
|
1193
1214
|
await page.waitForTimeout(20);
|
|
@@ -1197,6 +1218,7 @@ async function dragScrollNode(page, node, pixels, direction, startPositionX, sta
|
|
|
1197
1218
|
await page.mouse.move(startPage.x + dx * i, startPage.y + dy * i);
|
|
1198
1219
|
await page.waitForTimeout(24);
|
|
1199
1220
|
}
|
|
1221
|
+
await page.waitForTimeout(300);
|
|
1200
1222
|
await page.mouse.up();
|
|
1201
1223
|
await page.waitForTimeout(180);
|
|
1202
1224
|
}
|
|
@@ -1861,17 +1883,23 @@ function attachPageDiagnostics(page) {
|
|
|
1861
1883
|
}
|
|
1862
1884
|
async function readSootsimPageState(page) {
|
|
1863
1885
|
try {
|
|
1864
|
-
return await page.evaluate(() =>
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1886
|
+
return await page.evaluate(() => {
|
|
1887
|
+
const bootMarks = Reflect.get(window, "__sootsimBootMarks");
|
|
1888
|
+
return {
|
|
1889
|
+
bodyText: document.body?.innerText?.slice(0, 500) ?? "",
|
|
1890
|
+
bootStage: document.documentElement.dataset.sootsimBootStage ?? null,
|
|
1891
|
+
bootMarks: Array.isArray(bootMarks) ? bootMarks.slice(-16) : [],
|
|
1892
|
+
capturedAtEpochMs: Date.now(),
|
|
1893
|
+
bridges: {
|
|
1894
|
+
sootsim: Boolean(window.SootSim),
|
|
1895
|
+
test: Boolean(window.__sootsimTest)
|
|
1896
|
+
},
|
|
1897
|
+
globals: Object.keys(window).filter((key) => key.startsWith("__sootsim") || key === "SootSim").sort(),
|
|
1898
|
+
readyState: document.readyState,
|
|
1899
|
+
title: document.title,
|
|
1900
|
+
url: window.location.href
|
|
1901
|
+
};
|
|
1902
|
+
});
|
|
1875
1903
|
} catch (error) {
|
|
1876
1904
|
return {
|
|
1877
1905
|
evaluateError: error instanceof Error ? error.message : String(error)
|
|
@@ -1936,7 +1964,6 @@ function getSootsimNavigationDiagnostics(page) {
|
|
|
1936
1964
|
var import_proof_frame = __toESM(require_proof_frame(), 1);
|
|
1937
1965
|
var BASE_URL = process.env.PEACH_URL || "http://localhost:5173";
|
|
1938
1966
|
var SCREENSHOT_DIR = process.env.PEACH_SCREENSHOT_DIR || path.join(process.cwd(), "test", "detox-driver", "screenshots");
|
|
1939
|
-
var DETOX_PLATFORM = process.env.PEACH_PLATFORM === "android" ? "android" : "ios";
|
|
1940
1967
|
var _browser = null;
|
|
1941
1968
|
var _reapBrowserNow = null;
|
|
1942
1969
|
var _context = null;
|
|
@@ -2031,85 +2058,83 @@ async function waitForSootsimSurfaceMetrics(page, timeout = 3e4) {
|
|
|
2031
2058
|
async function waitForSootsimShellReady(page, timeout = 3e4) {
|
|
2032
2059
|
const expectsInitialAppPaint = isAppBootTargetUrl(page.url());
|
|
2033
2060
|
try {
|
|
2034
|
-
await page.evaluate(
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2061
|
+
await page.evaluate(
|
|
2062
|
+
async ({ timeoutMs, expectsInitialAppPaint: expectsInitialAppPaint2 }) => {
|
|
2063
|
+
let deadline = Date.now() + timeoutMs;
|
|
2064
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2065
|
+
const remaining = () => Math.max(1, deadline - Date.now());
|
|
2066
|
+
const withTimeout = async (label, promise) => {
|
|
2067
|
+
let timer = null;
|
|
2068
|
+
try {
|
|
2069
|
+
await Promise.race([
|
|
2070
|
+
promise,
|
|
2071
|
+
new Promise((_, reject) => {
|
|
2072
|
+
timer = setTimeout(
|
|
2073
|
+
() => reject(new Error(`${label} timed out after ${timeoutMs}ms`)),
|
|
2074
|
+
remaining()
|
|
2075
|
+
);
|
|
2076
|
+
})
|
|
2077
|
+
]);
|
|
2078
|
+
} finally {
|
|
2079
|
+
if (timer) clearTimeout(timer);
|
|
2080
|
+
}
|
|
2081
|
+
};
|
|
2082
|
+
const isTenantOnly = new URL(window.location.href).searchParams.get("renderMode") === "tenant-only";
|
|
2083
|
+
let shellHost = null;
|
|
2084
|
+
while (Date.now() < deadline) {
|
|
2085
|
+
const candidate = window.SootSim?.bridges?.shellHost;
|
|
2086
|
+
if (candidate || isTenantOnly) {
|
|
2087
|
+
shellHost = candidate ?? null;
|
|
2088
|
+
break;
|
|
2089
|
+
}
|
|
2090
|
+
await sleep(25);
|
|
2053
2091
|
}
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
while (Date.now() < deadline) {
|
|
2058
|
-
const candidate = window.SootSim?.bridges?.shellHost;
|
|
2059
|
-
if (candidate || isTenantOnly) {
|
|
2060
|
-
shellHost = candidate ?? null;
|
|
2061
|
-
break;
|
|
2092
|
+
if (!shellHost) {
|
|
2093
|
+
if (isTenantOnly) return;
|
|
2094
|
+
throw new Error(`sootsim shell host unavailable after ${timeoutMs}ms`);
|
|
2062
2095
|
}
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
if (
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
await withTimeout("sootsim shell ready", shellHost.ready);
|
|
2071
|
-
}
|
|
2072
|
-
const isRenderHostDisposed = (error) => error instanceof Error && error.message.includes("render host disposed");
|
|
2073
|
-
for (; ; ) {
|
|
2074
|
-
try {
|
|
2075
|
-
if (expectsInitialAppPaint2) {
|
|
2076
|
-
while (Date.now() < deadline) {
|
|
2077
|
-
if (document.documentElement.dataset.sootsimBootStage === "painted") {
|
|
2078
|
-
break;
|
|
2079
|
-
}
|
|
2080
|
-
await sleep(50);
|
|
2081
|
-
}
|
|
2082
|
-
if (document.documentElement.dataset.sootsimBootStage !== "painted") {
|
|
2083
|
-
throw new Error(
|
|
2084
|
-
`sootsim app first content paint timed out after ${timeoutMs}ms`
|
|
2085
|
-
);
|
|
2086
|
-
}
|
|
2087
|
-
} else if (shellHost.firstContentPainted && typeof shellHost.firstContentPainted.then === "function") {
|
|
2088
|
-
await withTimeout(
|
|
2089
|
-
"sootsim shell first content paint",
|
|
2090
|
-
shellHost.firstContentPainted
|
|
2091
|
-
);
|
|
2096
|
+
if (shellHost.ready && typeof shellHost.ready.then === "function") {
|
|
2097
|
+
await withTimeout("sootsim shell ready", shellHost.ready);
|
|
2098
|
+
}
|
|
2099
|
+
if (expectsInitialAppPaint2) {
|
|
2100
|
+
const compositorHost = Reflect.get(window, "__sootsimCompositor");
|
|
2101
|
+
if (typeof compositorHost?.firstContentPainted?.then !== "function") {
|
|
2102
|
+
throw new Error("sootsim app compositor unavailable at boot");
|
|
2092
2103
|
}
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2104
|
+
await compositorHost.firstContentPainted;
|
|
2105
|
+
deadline = Date.now() + 3e3;
|
|
2106
|
+
}
|
|
2107
|
+
const isRenderHostDisposed = (error) => error instanceof Error && error.message.includes("render host disposed");
|
|
2108
|
+
for (; ; ) {
|
|
2109
|
+
try {
|
|
2110
|
+
if (!expectsInitialAppPaint2 && shellHost.firstContentPainted && typeof shellHost.firstContentPainted.then === "function") {
|
|
2111
|
+
await withTimeout(
|
|
2112
|
+
"sootsim shell first content paint",
|
|
2113
|
+
shellHost.firstContentPainted
|
|
2103
2114
|
);
|
|
2104
2115
|
}
|
|
2116
|
+
const waitForScreenTransitions = window.__sootsimTest?.waitForScreenTransitions;
|
|
2117
|
+
if (typeof waitForScreenTransitions === "function") {
|
|
2118
|
+
const transitionResult = await waitForScreenTransitions({
|
|
2119
|
+
timeoutMs: Math.min(remaining(), 3e3),
|
|
2120
|
+
settleMs: 64,
|
|
2121
|
+
startWindowMs: 700
|
|
2122
|
+
});
|
|
2123
|
+
if (transitionResult?.timedOut === true) {
|
|
2124
|
+
throw new Error(
|
|
2125
|
+
`sootsim app launch transitions timed out: ${JSON.stringify(transitionResult)}`
|
|
2126
|
+
);
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
break;
|
|
2130
|
+
} catch (error) {
|
|
2131
|
+
if (!isRenderHostDisposed(error) || Date.now() >= deadline) throw error;
|
|
2132
|
+
await sleep(100);
|
|
2105
2133
|
}
|
|
2106
|
-
break;
|
|
2107
|
-
} catch (error) {
|
|
2108
|
-
if (!isRenderHostDisposed(error) || Date.now() >= deadline) throw error;
|
|
2109
|
-
await sleep(100);
|
|
2110
2134
|
}
|
|
2111
|
-
}
|
|
2112
|
-
|
|
2135
|
+
},
|
|
2136
|
+
{ timeoutMs: timeout, expectsInitialAppPaint }
|
|
2137
|
+
);
|
|
2113
2138
|
} catch (error) {
|
|
2114
2139
|
throw await describeSootsimBridgeFailure(page, error);
|
|
2115
2140
|
}
|
|
@@ -2418,7 +2443,7 @@ async function dispatchTapAtSootsimPoint(page, x, y) {
|
|
|
2418
2443
|
}
|
|
2419
2444
|
async function isFocusedTextInputNode(page, nodeInfo) {
|
|
2420
2445
|
const focused = await page.evaluate(
|
|
2421
|
-
() => window.__sootsimTest.getFocusedNode?.() ?? null
|
|
2446
|
+
async () => await window.__sootsimTest.getFocusedNode?.() ?? null
|
|
2422
2447
|
);
|
|
2423
2448
|
return !!focused && typeof focused === "object" && focused.nodeId === nodeInfo?.nodeId;
|
|
2424
2449
|
}
|
|
@@ -2430,7 +2455,14 @@ async function focusElementForTextEntry(el) {
|
|
|
2430
2455
|
}
|
|
2431
2456
|
if (await isFocusedTextInputNode(page, node)) return;
|
|
2432
2457
|
await el.tap();
|
|
2433
|
-
await
|
|
2458
|
+
await pollUntil(
|
|
2459
|
+
() => page.evaluate(async () => (await window.__sootsimTest.getFocusedNode?.())?.nodeId ?? null),
|
|
2460
|
+
(focusedNodeId) => focusedNodeId === node.nodeId,
|
|
2461
|
+
{
|
|
2462
|
+
timeoutMs: 5e3,
|
|
2463
|
+
label: `SootSim text input ${node.nodeId} to focus`
|
|
2464
|
+
}
|
|
2465
|
+
);
|
|
2434
2466
|
}
|
|
2435
2467
|
function ensureScreenshotDir() {
|
|
2436
2468
|
if (!fs.existsSync(SCREENSHOT_DIR)) {
|
|
@@ -2512,9 +2544,9 @@ async function captureSootsimPng(opts) {
|
|
|
2512
2544
|
await setSootsimCursorCaptureActive(page, false);
|
|
2513
2545
|
}
|
|
2514
2546
|
}
|
|
2515
|
-
async function writeSootsimScreenshot(
|
|
2547
|
+
async function writeSootsimScreenshot(name2, opts) {
|
|
2516
2548
|
ensureScreenshotDir();
|
|
2517
|
-
const screenshotPath = path.join(SCREENSHOT_DIR, `${
|
|
2549
|
+
const screenshotPath = path.join(SCREENSHOT_DIR, `${name2}.png`);
|
|
2518
2550
|
fs.writeFileSync(screenshotPath, await captureSootsimPng(opts));
|
|
2519
2551
|
return screenshotPath;
|
|
2520
2552
|
}
|
|
@@ -2533,6 +2565,24 @@ async function dispatchStatusBarOverride(config) {
|
|
|
2533
2565
|
);
|
|
2534
2566
|
await page.waitForTimeout(50);
|
|
2535
2567
|
}
|
|
2568
|
+
async function dispatchDeviceOrientation(orientation) {
|
|
2569
|
+
const page = getPage();
|
|
2570
|
+
await page.evaluate(
|
|
2571
|
+
({ eventType, next, landscapeSide }) => {
|
|
2572
|
+
window.dispatchEvent(
|
|
2573
|
+
new CustomEvent(eventType, {
|
|
2574
|
+
detail: { type: "set-device-orientation", orientation: next, landscapeSide }
|
|
2575
|
+
})
|
|
2576
|
+
);
|
|
2577
|
+
},
|
|
2578
|
+
{
|
|
2579
|
+
eventType: SOOTSIM_SHELL_COMMAND_NAME,
|
|
2580
|
+
next: orientation,
|
|
2581
|
+
landscapeSide: DETOX_PLATFORM === "ios" ? "right" : "left"
|
|
2582
|
+
}
|
|
2583
|
+
);
|
|
2584
|
+
await page.waitForTimeout(50);
|
|
2585
|
+
}
|
|
2536
2586
|
var MAX_SETTLED_SPEED_PT_PER_SEC = 2;
|
|
2537
2587
|
async function waitForFrameClock(page, minMs = 25) {
|
|
2538
2588
|
const start = performance.now();
|
|
@@ -2686,10 +2736,7 @@ function createSootSimElement(matcher) {
|
|
|
2686
2736
|
if (typeof longPress !== "function") {
|
|
2687
2737
|
throw new Error("sootsim longPress bridge is not installed");
|
|
2688
2738
|
}
|
|
2689
|
-
|
|
2690
|
-
if (!hit) {
|
|
2691
|
-
throw new Error(`sootsim longPress missed at ${x},${y}`);
|
|
2692
|
-
}
|
|
2739
|
+
await longPress(x, y, duration2);
|
|
2693
2740
|
},
|
|
2694
2741
|
{ duration, x: target.x, y: target.y }
|
|
2695
2742
|
);
|
|
@@ -2714,15 +2761,25 @@ function createSootSimElement(matcher) {
|
|
|
2714
2761
|
const endSootSimY = tgtNode.absolutePosition.y + tgtNode.layout.height * normalizedEndY;
|
|
2715
2762
|
const startPage = await sootsimToPage(page, startSootSimX, startSootSimY);
|
|
2716
2763
|
const endPage = await sootsimToPage(page, endSootSimX, endSootSimY);
|
|
2717
|
-
const
|
|
2764
|
+
const pace = 25 * (speed === "slow" ? 0.1 : 0.5);
|
|
2765
|
+
const steps = Math.max(
|
|
2766
|
+
1,
|
|
2767
|
+
Math.round(
|
|
2768
|
+
Math.max(
|
|
2769
|
+
Math.abs(endSootSimX - startSootSimX),
|
|
2770
|
+
Math.abs(endSootSimY - startSootSimY)
|
|
2771
|
+
) / pace
|
|
2772
|
+
)
|
|
2773
|
+
);
|
|
2774
|
+
const frameMs = 1e3 / 60;
|
|
2718
2775
|
await page.mouse.move(startPage.x, startPage.y);
|
|
2719
2776
|
await page.mouse.down();
|
|
2720
|
-
await page.waitForTimeout(
|
|
2777
|
+
await page.waitForTimeout(duration);
|
|
2721
2778
|
const dx = (endPage.x - startPage.x) / steps;
|
|
2722
2779
|
const dy = (endPage.y - startPage.y) / steps;
|
|
2723
|
-
for (let i = 1; i <= steps; i++) {
|
|
2780
|
+
for (let i = 1; i <= steps + 1; i++) {
|
|
2781
|
+
await page.waitForTimeout(i === steps + 1 ? holdDuration : frameMs);
|
|
2724
2782
|
await page.mouse.move(startPage.x + dx * i, startPage.y + dy * i);
|
|
2725
|
-
await page.waitForTimeout(speed === "slow" ? 30 : 10);
|
|
2726
2783
|
}
|
|
2727
2784
|
await page.mouse.up();
|
|
2728
2785
|
await page.waitForTimeout(50);
|
|
@@ -2747,6 +2804,7 @@ function createSootSimElement(matcher) {
|
|
|
2747
2804
|
if (replaced === true) return;
|
|
2748
2805
|
}
|
|
2749
2806
|
await focusElementForTextEntry(el);
|
|
2807
|
+
await page.keyboard.press("End");
|
|
2750
2808
|
const value = typeof node?.text === "string" ? node.text : "";
|
|
2751
2809
|
for (let i = 0; i < value.length; i++) {
|
|
2752
2810
|
await page.keyboard.press("Backspace");
|
|
@@ -2756,6 +2814,7 @@ function createSootSimElement(matcher) {
|
|
|
2756
2814
|
async clearText() {
|
|
2757
2815
|
const page = getPage();
|
|
2758
2816
|
await focusElementForTextEntry(el);
|
|
2817
|
+
await page.keyboard.press("End");
|
|
2759
2818
|
const node = await findNodeByMatcher(matcher);
|
|
2760
2819
|
const value = typeof node?.text === "string" ? node.text : "";
|
|
2761
2820
|
for (let i = 0; i < value.length; i++) {
|
|
@@ -2982,7 +3041,7 @@ function createSootSimElement(matcher) {
|
|
|
2982
3041
|
stepDelayMs: speed === "fast" ? 14 : 24
|
|
2983
3042
|
});
|
|
2984
3043
|
},
|
|
2985
|
-
async takeScreenshot(
|
|
3044
|
+
async takeScreenshot(name2) {
|
|
2986
3045
|
const node = await findNodeByMatcher(matcher);
|
|
2987
3046
|
if (!node)
|
|
2988
3047
|
throw new Error(
|
|
@@ -2994,7 +3053,7 @@ function createSootSimElement(matcher) {
|
|
|
2994
3053
|
width: node.layout.width,
|
|
2995
3054
|
height: node.layout.height
|
|
2996
3055
|
};
|
|
2997
|
-
return writeSootsimScreenshot(
|
|
3056
|
+
return writeSootsimScreenshot(name2, {
|
|
2998
3057
|
crop: {
|
|
2999
3058
|
x: box.x,
|
|
3000
3059
|
y: box.y,
|
|
@@ -3160,14 +3219,14 @@ var device = {
|
|
|
3160
3219
|
await waitForSootsimShellReady(page, 1e4);
|
|
3161
3220
|
await waitForSootsimWorkletsIdle(page, 3e3);
|
|
3162
3221
|
},
|
|
3163
|
-
async takeScreenshot(
|
|
3164
|
-
return writeSootsimScreenshot(
|
|
3222
|
+
async takeScreenshot(name2) {
|
|
3223
|
+
return writeSootsimScreenshot(name2);
|
|
3165
3224
|
},
|
|
3166
3225
|
// single-shot variant for multi-stage animation captures. skips the
|
|
3167
3226
|
// frame-stability poll so callers can sample mid-transition.
|
|
3168
|
-
async takeScreenshotFast(
|
|
3227
|
+
async takeScreenshotFast(name2) {
|
|
3169
3228
|
ensureScreenshotDir();
|
|
3170
|
-
const screenshotPath = path.join(SCREENSHOT_DIR, `${
|
|
3229
|
+
const screenshotPath = path.join(SCREENSHOT_DIR, `${name2}.png`);
|
|
3171
3230
|
fs.writeFileSync(screenshotPath, await captureSootsimPngFast());
|
|
3172
3231
|
return screenshotPath;
|
|
3173
3232
|
},
|
|
@@ -3337,6 +3396,7 @@ var device = {
|
|
|
3337
3396
|
snapshot.bitmap.close();
|
|
3338
3397
|
return {
|
|
3339
3398
|
actualMs: snapshot.actualMs,
|
|
3399
|
+
anchorId: snapshot.anchorId,
|
|
3340
3400
|
dataUrl: canvas.toDataURL("image/png"),
|
|
3341
3401
|
label: snapshot.label,
|
|
3342
3402
|
offsetMs: snapshot.offsetMs,
|
|
@@ -3381,6 +3441,7 @@ var device = {
|
|
|
3381
3441
|
holdState.released = true;
|
|
3382
3442
|
await interact.touchUp(holdState.x, holdState.y);
|
|
3383
3443
|
};
|
|
3444
|
+
const keptFrames = /* @__PURE__ */ new Map();
|
|
3384
3445
|
try {
|
|
3385
3446
|
const baselineBitmap = await captureBitmap();
|
|
3386
3447
|
const baseline = regionPixels(baselineBitmap);
|
|
@@ -3389,7 +3450,9 @@ var device = {
|
|
|
3389
3450
|
if (input.hold && interact) {
|
|
3390
3451
|
const hit = await interact.touchDown(input.hold.x, input.hold.y);
|
|
3391
3452
|
if (!hit) {
|
|
3392
|
-
throw new Error(
|
|
3453
|
+
throw new Error(
|
|
3454
|
+
`peach motion hold missed at ${input.hold.x},${input.hold.y}`
|
|
3455
|
+
);
|
|
3393
3456
|
}
|
|
3394
3457
|
} else {
|
|
3395
3458
|
while (!state.triggerCompleted) {
|
|
@@ -3399,6 +3462,8 @@ var device = {
|
|
|
3399
3462
|
}
|
|
3400
3463
|
let visibleStart = null;
|
|
3401
3464
|
let detectedMeasurement = null;
|
|
3465
|
+
let previousReceipt = null;
|
|
3466
|
+
let previousBitmap = null;
|
|
3402
3467
|
const detectionFrameLimit = Math.ceil(input.detectionTimeoutMs * 60 / 1e3);
|
|
3403
3468
|
for (let frame = 0; frame < detectionFrameLimit; frame++) {
|
|
3404
3469
|
if (state.cancelled) throw new Error("peach motion capture was cancelled");
|
|
@@ -3414,29 +3479,137 @@ var device = {
|
|
|
3414
3479
|
label: "SootSim visible-change detector",
|
|
3415
3480
|
width: bounds.width
|
|
3416
3481
|
});
|
|
3417
|
-
candidateBitmap.close();
|
|
3418
3482
|
if (measurement.passedMinimum) {
|
|
3483
|
+
candidateBitmap.close();
|
|
3419
3484
|
visibleStart = receipt;
|
|
3420
3485
|
detectedMeasurement = measurement;
|
|
3421
3486
|
break;
|
|
3422
3487
|
}
|
|
3488
|
+
previousBitmap?.close();
|
|
3489
|
+
previousBitmap = candidateBitmap;
|
|
3490
|
+
previousReceipt = receipt;
|
|
3491
|
+
}
|
|
3492
|
+
if (previousReceipt && previousBitmap) {
|
|
3493
|
+
keptFrames.set(previousReceipt.frameIndex, previousBitmap);
|
|
3423
3494
|
}
|
|
3424
3495
|
if (!visibleStart || !detectedMeasurement) {
|
|
3425
3496
|
throw new Error(
|
|
3426
3497
|
`peach motion had no visible change before ${input.detectionTimeoutMs}ms`
|
|
3427
3498
|
);
|
|
3428
3499
|
}
|
|
3500
|
+
const driveStart = previousReceipt ?? visibleStart;
|
|
3501
|
+
let currentReceipt = visibleStart;
|
|
3502
|
+
const resolvedAnchors = /* @__PURE__ */ new Map();
|
|
3503
|
+
const anchorRecords = [];
|
|
3504
|
+
const resolveAnchor = async (stage) => {
|
|
3505
|
+
const anchor = stage.anchor;
|
|
3506
|
+
if (!anchor) return { anchorId: "transition", receipt: driveStart };
|
|
3507
|
+
const known = resolvedAnchors.get(anchor.id);
|
|
3508
|
+
if (known) return { anchorId: anchor.id, receipt: known };
|
|
3509
|
+
if (typeof anchor.notBeforeMs !== "number" || !Number.isFinite(anchor.notBeforeMs) || anchor.notBeforeMs < 0) {
|
|
3510
|
+
throw new Error(
|
|
3511
|
+
`peach motion stage ${stage.label} anchor ${anchor.id} needs a non-negative notBeforeMs`
|
|
3512
|
+
);
|
|
3513
|
+
}
|
|
3514
|
+
const onsetThresholdPercent = anchor.onsetThresholdPercent ?? 5;
|
|
3515
|
+
const searchStartVts = driveStart.virtualTimestampMs + anchor.notBeforeMs;
|
|
3516
|
+
if (currentReceipt.virtualTimestampMs > searchStartVts) {
|
|
3517
|
+
throw new Error(
|
|
3518
|
+
`peach motion anchor ${anchor.id} searches from ${anchor.notBeforeMs}ms but the clock already sits past it: the ladder is unexecutable`
|
|
3519
|
+
);
|
|
3520
|
+
}
|
|
3521
|
+
let searchReceipt = currentReceipt;
|
|
3522
|
+
while (searchReceipt.virtualTimestampMs < searchStartVts) {
|
|
3523
|
+
if (state.cancelled) throw new Error("peach motion capture was cancelled");
|
|
3524
|
+
searchReceipt = await controller.stepFrame();
|
|
3525
|
+
await advanceHold(searchReceipt);
|
|
3526
|
+
}
|
|
3527
|
+
currentReceipt = searchReceipt;
|
|
3528
|
+
let anchorBitmap = await captureBitmap();
|
|
3529
|
+
let previousPixels = regionPixels(anchorBitmap);
|
|
3530
|
+
let anchorReceipt = searchReceipt;
|
|
3531
|
+
let onsetMeasurement = null;
|
|
3532
|
+
let panPeakPercent = 0;
|
|
3533
|
+
let latePeakPercent = 0;
|
|
3534
|
+
for (let frame = 0; frame < detectionFrameLimit; frame++) {
|
|
3535
|
+
if (state.cancelled) throw new Error("peach motion capture was cancelled");
|
|
3536
|
+
const receipt = await controller.stepFrame();
|
|
3537
|
+
await advanceHold(receipt);
|
|
3538
|
+
const candidateBitmap = await captureBitmap();
|
|
3539
|
+
const bounds = boundsFor(candidateBitmap);
|
|
3540
|
+
const candidate = regionPixels(candidateBitmap);
|
|
3541
|
+
const measurement = measureChange({
|
|
3542
|
+
baseline: previousPixels,
|
|
3543
|
+
candidate,
|
|
3544
|
+
definition: {
|
|
3545
|
+
...input.motionChangeDefinition,
|
|
3546
|
+
minimumChangePercent: onsetThresholdPercent
|
|
3547
|
+
},
|
|
3548
|
+
height: bounds.height,
|
|
3549
|
+
label: `SootSim second-drive detector ${anchor.id}`,
|
|
3550
|
+
width: bounds.width
|
|
3551
|
+
});
|
|
3552
|
+
previousPixels = candidate;
|
|
3553
|
+
currentReceipt = receipt;
|
|
3554
|
+
if (receipt.virtualTimestampMs - searchStartVts < 250) {
|
|
3555
|
+
panPeakPercent = Math.max(panPeakPercent, measurement.changePercent);
|
|
3556
|
+
} else {
|
|
3557
|
+
latePeakPercent = Math.max(latePeakPercent, measurement.changePercent);
|
|
3558
|
+
}
|
|
3559
|
+
if (measurement.passedMinimum) {
|
|
3560
|
+
candidateBitmap.close();
|
|
3561
|
+
onsetMeasurement = measurement;
|
|
3562
|
+
break;
|
|
3563
|
+
}
|
|
3564
|
+
anchorBitmap.close();
|
|
3565
|
+
anchorBitmap = candidateBitmap;
|
|
3566
|
+
anchorReceipt = receipt;
|
|
3567
|
+
}
|
|
3568
|
+
if (!onsetMeasurement) {
|
|
3569
|
+
anchorBitmap.close();
|
|
3570
|
+
throw new Error(
|
|
3571
|
+
`peach motion anchor ${anchor.id} found no onset past ${onsetThresholdPercent}% within ${input.detectionTimeoutMs}ms of ${anchor.notBeforeMs}ms (pan peak ${panPeakPercent.toFixed(2)}%, later peak ${latePeakPercent.toFixed(2)}%): the gate or the search start is wrong for this surface`
|
|
3572
|
+
);
|
|
3573
|
+
}
|
|
3574
|
+
keptFrames.get(anchorReceipt.frameIndex)?.close();
|
|
3575
|
+
keptFrames.set(anchorReceipt.frameIndex, anchorBitmap);
|
|
3576
|
+
resolvedAnchors.set(anchor.id, anchorReceipt);
|
|
3577
|
+
anchorRecords.push({
|
|
3578
|
+
changedPixelPercent: onsetMeasurement.changePercent,
|
|
3579
|
+
frameIndex: anchorReceipt.frameIndex,
|
|
3580
|
+
id: anchor.id,
|
|
3581
|
+
notBeforeMs: anchor.notBeforeMs,
|
|
3582
|
+
virtualTimestampMs: anchorReceipt.virtualTimestampMs
|
|
3583
|
+
});
|
|
3584
|
+
return { anchorId: anchor.id, receipt: anchorReceipt };
|
|
3585
|
+
};
|
|
3429
3586
|
const snapshots = [];
|
|
3430
3587
|
for (const stage of input.stages) {
|
|
3431
3588
|
if (state.cancelled) throw new Error("peach motion capture was cancelled");
|
|
3432
|
-
const
|
|
3589
|
+
const { anchorId, receipt: anchorReceipt } = await resolveAnchor(stage);
|
|
3590
|
+
const stageFrameIndex = anchorReceipt.frameIndex + Math.ceil(stage.offsetMs / frameMs);
|
|
3591
|
+
const kept = keptFrames.get(stageFrameIndex);
|
|
3592
|
+
if (kept && stageFrameIndex < currentReceipt.frameIndex) {
|
|
3593
|
+
snapshots.push({
|
|
3594
|
+
actualMs: 0,
|
|
3595
|
+
anchorId,
|
|
3596
|
+
bitmap: await createImageBitmap(kept),
|
|
3597
|
+
frameIndex: anchorReceipt.frameIndex,
|
|
3598
|
+
label: stage.label,
|
|
3599
|
+
offsetMs: stage.offsetMs,
|
|
3600
|
+
virtualTimestampMs: anchorReceipt.virtualTimestampMs
|
|
3601
|
+
});
|
|
3602
|
+
continue;
|
|
3603
|
+
}
|
|
3433
3604
|
for (let eventFrameIndex = nextHoldEventFrameIndex(); eventFrameIndex !== null && stageFrameIndex > eventFrameIndex; eventFrameIndex = nextHoldEventFrameIndex()) {
|
|
3434
3605
|
await advanceHold(await controller.stepFrame());
|
|
3435
3606
|
}
|
|
3436
|
-
const receipt = await controller.stepToOffset(
|
|
3607
|
+
const receipt = await controller.stepToOffset(anchorReceipt, stage.offsetMs);
|
|
3437
3608
|
await advanceHold(receipt);
|
|
3609
|
+
currentReceipt = receipt;
|
|
3438
3610
|
snapshots.push({
|
|
3439
|
-
actualMs: receipt.virtualTimestampMs -
|
|
3611
|
+
actualMs: receipt.virtualTimestampMs - anchorReceipt.virtualTimestampMs,
|
|
3612
|
+
anchorId,
|
|
3440
3613
|
bitmap: await captureBitmap(),
|
|
3441
3614
|
frameIndex: receipt.frameIndex,
|
|
3442
3615
|
label: stage.label,
|
|
@@ -3444,14 +3617,28 @@ var device = {
|
|
|
3444
3617
|
virtualTimestampMs: receipt.virtualTimestampMs
|
|
3445
3618
|
});
|
|
3446
3619
|
}
|
|
3620
|
+
let releasedOffsetMs = 0;
|
|
3621
|
+
while (nextHoldEventFrameIndex() !== null) {
|
|
3622
|
+
const receipt = await controller.stepFrame();
|
|
3623
|
+
await advanceHold(receipt);
|
|
3624
|
+
releasedOffsetMs = receipt.virtualTimestampMs - driveStart.virtualTimestampMs;
|
|
3625
|
+
}
|
|
3447
3626
|
if (input.settleMs > 0) {
|
|
3448
|
-
const
|
|
3449
|
-
(maximum,
|
|
3450
|
-
|
|
3627
|
+
const maximumPrimaryMs = snapshots.reduce(
|
|
3628
|
+
(maximum, snapshot) => Math.max(
|
|
3629
|
+
maximum,
|
|
3630
|
+
snapshot.virtualTimestampMs - driveStart.virtualTimestampMs
|
|
3631
|
+
),
|
|
3632
|
+
releasedOffsetMs
|
|
3451
3633
|
);
|
|
3452
|
-
await controller.stepToOffset(
|
|
3634
|
+
await controller.stepToOffset(driveStart, maximumPrimaryMs + input.settleMs);
|
|
3453
3635
|
}
|
|
3454
3636
|
return {
|
|
3637
|
+
anchors: anchorRecords,
|
|
3638
|
+
driveStart: {
|
|
3639
|
+
frameIndex: driveStart.frameIndex,
|
|
3640
|
+
virtualTimestampMs: driveStart.virtualTimestampMs
|
|
3641
|
+
},
|
|
3455
3642
|
frames: snapshots.map(encode),
|
|
3456
3643
|
visibleChangeDetection: {
|
|
3457
3644
|
actualMs: visibleStart.frameIndex * 1e3 / 60,
|
|
@@ -3465,6 +3652,7 @@ var device = {
|
|
|
3465
3652
|
}
|
|
3466
3653
|
};
|
|
3467
3654
|
} finally {
|
|
3655
|
+
for (const bitmap of keptFrames.values()) bitmap.close();
|
|
3468
3656
|
controller.end();
|
|
3469
3657
|
delete registry[input.token];
|
|
3470
3658
|
}
|
|
@@ -3506,6 +3694,8 @@ var device = {
|
|
|
3506
3694
|
}, token);
|
|
3507
3695
|
const capture = await capturePromise;
|
|
3508
3696
|
return {
|
|
3697
|
+
anchors: capture.anchors,
|
|
3698
|
+
driveStart: capture.driveStart,
|
|
3509
3699
|
frames: writeFastScreenshotFrames(namePrefix, capture.frames),
|
|
3510
3700
|
triggerCompletedMs,
|
|
3511
3701
|
triggerStartMs,
|
|
@@ -3739,6 +3929,15 @@ var device = {
|
|
|
3739
3929
|
async setStatusBar(config) {
|
|
3740
3930
|
await dispatchStatusBarOverride(config);
|
|
3741
3931
|
},
|
|
3932
|
+
async setOrientation(orientation) {
|
|
3933
|
+
const next = orientation === "portrait" ? "portrait" : orientation === "landscape" ? "landscape" : null;
|
|
3934
|
+
if (!next) {
|
|
3935
|
+
throw new Error(
|
|
3936
|
+
`sootsim device.setOrientation supports portrait and landscape; got ${orientation}`
|
|
3937
|
+
);
|
|
3938
|
+
}
|
|
3939
|
+
await dispatchDeviceOrientation(next);
|
|
3940
|
+
},
|
|
3742
3941
|
async setURLBlacklist(urls) {
|
|
3743
3942
|
},
|
|
3744
3943
|
async enableSynchronization() {
|