@ghia/native 0.0.0 → 0.11.0
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 +201 -0
- package/browser.mjs +7 -0
- package/dist/aria.d.ts +72 -0
- package/dist/aria.d.ts.map +1 -0
- package/dist/aria.js +2 -0
- package/dist/aria.js.map +1 -0
- package/dist/automation/client.d.ts +243 -0
- package/dist/automation/client.d.ts.map +1 -0
- package/dist/automation/client.js +801 -0
- package/dist/automation/client.js.map +1 -0
- package/dist/automation/index.d.ts +5 -0
- package/dist/automation/index.d.ts.map +1 -0
- package/dist/automation/index.js +4 -0
- package/dist/automation/index.js.map +1 -0
- package/dist/automation/protocol.d.ts +309 -0
- package/dist/automation/protocol.d.ts.map +1 -0
- package/dist/automation/protocol.js +320 -0
- package/dist/automation/protocol.js.map +1 -0
- package/dist/blob-images.d.ts +32 -0
- package/dist/blob-images.d.ts.map +1 -0
- package/dist/blob-images.js +55 -0
- package/dist/blob-images.js.map +1 -0
- package/dist/bounds.d.ts +61 -0
- package/dist/bounds.d.ts.map +1 -0
- package/dist/bounds.js +85 -0
- package/dist/bounds.js.map +1 -0
- package/dist/clipboard-global.d.ts +11 -0
- package/dist/clipboard-global.d.ts.map +1 -0
- package/dist/clipboard-global.js +2 -0
- package/dist/clipboard-global.js.map +1 -0
- package/dist/clipboard.d.ts +19 -0
- package/dist/clipboard.d.ts.map +1 -0
- package/dist/clipboard.js +41 -0
- package/dist/clipboard.js.map +1 -0
- package/dist/cpu-throttle.d.ts +6 -0
- package/dist/cpu-throttle.d.ts.map +1 -0
- package/dist/cpu-throttle.js +42 -0
- package/dist/cpu-throttle.js.map +1 -0
- package/dist/events/data-transfer.d.ts +23 -0
- package/dist/events/data-transfer.d.ts.map +1 -0
- package/dist/events/data-transfer.js +97 -0
- package/dist/events/data-transfer.js.map +1 -0
- package/dist/events/dispatch.d.ts +144 -0
- package/dist/events/dispatch.d.ts.map +1 -0
- package/dist/events/dispatch.js +241 -0
- package/dist/events/dispatch.js.map +1 -0
- package/dist/events/drag.d.ts +21 -0
- package/dist/events/drag.d.ts.map +1 -0
- package/dist/events/drag.js +111 -0
- package/dist/events/drag.js.map +1 -0
- package/dist/events/event-props.d.ts +19 -0
- package/dist/events/event-props.d.ts.map +1 -0
- package/dist/events/event-props.js +104 -0
- package/dist/events/event-props.js.map +1 -0
- package/dist/events/keyboard-defaults.d.ts +18 -0
- package/dist/events/keyboard-defaults.d.ts.map +1 -0
- package/dist/events/keyboard-defaults.js +121 -0
- package/dist/events/keyboard-defaults.js.map +1 -0
- package/dist/events/listeners.d.ts +18 -0
- package/dist/events/listeners.d.ts.map +1 -0
- package/dist/events/listeners.js +49 -0
- package/dist/events/listeners.js.map +1 -0
- package/dist/events/route.d.ts +17 -0
- package/dist/events/route.d.ts.map +1 -0
- package/dist/events/route.js +36 -0
- package/dist/events/route.js.map +1 -0
- package/dist/events/types.d.ts +269 -0
- package/dist/events/types.d.ts.map +1 -0
- package/dist/events/types.js +2 -0
- package/dist/events/types.js.map +1 -0
- package/dist/events/window-target.d.ts +120 -0
- package/dist/events/window-target.d.ts.map +1 -0
- package/dist/events/window-target.js +311 -0
- package/dist/events/window-target.js.map +1 -0
- package/dist/eyedropper.d.ts +33 -0
- package/dist/eyedropper.d.ts.map +1 -0
- package/dist/eyedropper.js +66 -0
- package/dist/eyedropper.js.map +1 -0
- package/dist/focus.d.ts +18 -0
- package/dist/focus.d.ts.map +1 -0
- package/dist/focus.js +26 -0
- package/dist/focus.js.map +1 -0
- package/dist/fonts.d.ts +12 -0
- package/dist/fonts.d.ts.map +1 -0
- package/dist/fonts.js +17 -0
- package/dist/fonts.js.map +1 -0
- package/dist/forms.d.ts +10 -0
- package/dist/forms.d.ts.map +1 -0
- package/dist/forms.js +9 -0
- package/dist/forms.js.map +1 -0
- package/dist/host-runtime.d.ts +108 -0
- package/dist/host-runtime.d.ts.map +1 -0
- package/dist/host-runtime.js +213 -0
- package/dist/host-runtime.js.map +1 -0
- package/dist/host.d.ts +2403 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +30 -0
- package/dist/host.js.map +1 -0
- package/dist/idle-gc.d.ts +36 -0
- package/dist/idle-gc.d.ts.map +1 -0
- package/dist/idle-gc.js +70 -0
- package/dist/idle-gc.js.map +1 -0
- package/dist/keyframes.d.ts +108 -0
- package/dist/keyframes.d.ts.map +1 -0
- package/dist/keyframes.js +107 -0
- package/dist/keyframes.js.map +1 -0
- package/dist/layout-animation.d.ts +38 -0
- package/dist/layout-animation.d.ts.map +1 -0
- package/dist/layout-animation.js +41 -0
- package/dist/layout-animation.js.map +1 -0
- package/dist/match-media.d.ts +62 -0
- package/dist/match-media.d.ts.map +1 -0
- package/dist/match-media.js +272 -0
- package/dist/match-media.js.map +1 -0
- package/dist/media-query.d.ts +21 -0
- package/dist/media-query.d.ts.map +1 -0
- package/dist/media-query.js +599 -0
- package/dist/media-query.js.map +1 -0
- package/dist/motion-bindings.d.ts +36 -0
- package/dist/motion-bindings.d.ts.map +1 -0
- package/dist/motion-bindings.js +107 -0
- package/dist/motion-bindings.js.map +1 -0
- package/dist/motion-config.d.ts +20 -0
- package/dist/motion-config.d.ts.map +1 -0
- package/dist/motion-config.js +51 -0
- package/dist/motion-config.js.map +1 -0
- package/dist/motion-span.d.ts +57 -0
- package/dist/motion-span.d.ts.map +1 -0
- package/dist/motion-span.js +150 -0
- package/dist/motion-span.js.map +1 -0
- package/dist/motion-value.d.ts +269 -0
- package/dist/motion-value.d.ts.map +1 -0
- package/dist/motion-value.js +941 -0
- package/dist/motion-value.js.map +1 -0
- package/dist/motion-variants.d.ts +190 -0
- package/dist/motion-variants.d.ts.map +1 -0
- package/dist/motion-variants.js +249 -0
- package/dist/motion-variants.js.map +1 -0
- package/dist/mutations.d.ts +65 -0
- package/dist/mutations.d.ts.map +1 -0
- package/dist/mutations.js +320 -0
- package/dist/mutations.js.map +1 -0
- package/dist/nativeview.d.ts +10 -0
- package/dist/nativeview.d.ts.map +1 -0
- package/dist/nativeview.js +92 -0
- package/dist/nativeview.js.map +1 -0
- package/dist/observers.d.ts +92 -0
- package/dist/observers.d.ts.map +1 -0
- package/dist/observers.js +579 -0
- package/dist/observers.js.map +1 -0
- package/dist/presence.d.ts +63 -0
- package/dist/presence.d.ts.map +1 -0
- package/dist/presence.js +54 -0
- package/dist/presence.js.map +1 -0
- package/dist/renderer-state.d.ts +155 -0
- package/dist/renderer-state.d.ts.map +1 -0
- package/dist/renderer-state.js +302 -0
- package/dist/renderer-state.js.map +1 -0
- package/dist/reorder.d.ts +63 -0
- package/dist/reorder.d.ts.map +1 -0
- package/dist/reorder.js +303 -0
- package/dist/reorder.js.map +1 -0
- package/dist/runtime.d.ts +23 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +157 -0
- package/dist/runtime.js.map +1 -0
- package/dist/screen.d.ts +8 -0
- package/dist/screen.d.ts.map +1 -0
- package/dist/screen.js +16 -0
- package/dist/screen.js.map +1 -0
- package/dist/scroll.d.ts +79 -0
- package/dist/scroll.d.ts.map +1 -0
- package/dist/scroll.js +170 -0
- package/dist/scroll.js.map +1 -0
- package/dist/style-images.d.ts +20 -0
- package/dist/style-images.d.ts.map +1 -0
- package/dist/style-images.js +57 -0
- package/dist/style-images.js.map +1 -0
- package/dist/svg.d.ts +16 -0
- package/dist/svg.d.ts.map +1 -0
- package/dist/svg.js +119 -0
- package/dist/svg.js.map +1 -0
- package/dist/testing.d.ts +811 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +1187 -0
- package/dist/testing.js.map +1 -0
- package/dist/text-match.d.ts +8 -0
- package/dist/text-match.d.ts.map +1 -0
- package/dist/text-match.js +10 -0
- package/dist/text-match.js.map +1 -0
- package/dist/text-selection.d.ts +34 -0
- package/dist/text-selection.d.ts.map +1 -0
- package/dist/text-selection.js +23 -0
- package/dist/text-selection.js.map +1 -0
- package/dist/universal-style.d.ts +22 -0
- package/dist/universal-style.d.ts.map +1 -0
- package/dist/universal-style.js +58 -0
- package/dist/universal-style.js.map +1 -0
- package/dist/web-animations.d.ts +300 -0
- package/dist/web-animations.d.ts.map +1 -0
- package/dist/web-animations.js +1032 -0
- package/dist/web-animations.js.map +1 -0
- package/dist/webview.d.ts +39 -0
- package/dist/webview.d.ts.map +1 -0
- package/dist/webview.js +26 -0
- package/dist/webview.js.map +1 -0
- package/gpuix-native.darwin-arm64.node +4 -0
- package/index.cjs +789 -0
- package/index.d.ts +2603 -0
- package/index.js +729 -0
- package/package.json +96 -4
package/dist/testing.js
ADDED
|
@@ -0,0 +1,1187 @@
|
|
|
1
|
+
/// GPUIX TestRenderer — thin wrapper over the native TestGpuixRenderer.
|
|
2
|
+
///
|
|
3
|
+
/// All state lives in Rust's RetainedTree. All mutations go directly to
|
|
4
|
+
/// the native renderer via napi. Inspection methods (findByType, getAllText,
|
|
5
|
+
/// toJSON, etc.) query the Rust tree via napi — no JS-side shadow copy.
|
|
6
|
+
///
|
|
7
|
+
/// All event simulation goes through the native GPUI pipeline (coordinate-based
|
|
8
|
+
/// hit testing, GPUI dispatch, emit_event_full). The nativeSimulate* methods
|
|
9
|
+
/// flush the tree, dispatch through GPUI, drain events, and feed them into
|
|
10
|
+
/// the injected framework event dispatcher.
|
|
11
|
+
import { hasTestGpuixRenderer, TestGpuixRenderer, measureMotionAnimation, validateKeyframes, } from "../index.js";
|
|
12
|
+
import { bindClipboard } from "./clipboard.js";
|
|
13
|
+
import { forgetElementSource, forgetWindowSource } from "./events/window-target.js";
|
|
14
|
+
import { matchableText } from "./text-match.js";
|
|
15
|
+
import { flushPendingMutations } from "./bounds.js";
|
|
16
|
+
import { closeWebAnimations } from "./web-animations.js";
|
|
17
|
+
import { flushObservations, settleObservations } from "./observers.js";
|
|
18
|
+
import { readFontFiles } from "./fonts.js";
|
|
19
|
+
import { registerKeyframesHost, unregisterKeyframesHost, registerKeyframesValidator, } from "./keyframes.js";
|
|
20
|
+
import { registerAnimationMeasurer } from "./motion-value.js";
|
|
21
|
+
import { createRendererState, reportSyncErrors } from "./renderer-state.js";
|
|
22
|
+
export { applyMacCpuThrottleFromEnv, MAC_CPU_THROTTLES, readMacCpuThrottle, } from "./cpu-throttle.js";
|
|
23
|
+
// The Linux stub has no test methods; the native availability check guards the full API.
|
|
24
|
+
function hasNativeTestRendererClass(renderer) {
|
|
25
|
+
return hasTestGpuixRenderer();
|
|
26
|
+
}
|
|
27
|
+
const NativeTestRenderer = hasNativeTestRendererClass(TestGpuixRenderer) ? TestGpuixRenderer : null;
|
|
28
|
+
/** Whether the native TestGpuixRenderer is available (for conditional test registration). */
|
|
29
|
+
export const hasNativeTestRenderer = NativeTestRenderer != null;
|
|
30
|
+
registerKeyframesValidator(validateKeyframes);
|
|
31
|
+
registerAnimationMeasurer((from, target, transition) => {
|
|
32
|
+
const length = measureMotionAnimation(from, target, transition ?? null);
|
|
33
|
+
return { duration: length.duration, iterationDuration: length.iterationDuration };
|
|
34
|
+
});
|
|
35
|
+
// ── TestRenderer ─────────────────────────────────────────────────────
|
|
36
|
+
/** The test window is one per thread: making a renderer tears down the one before it. */
|
|
37
|
+
let latestRenderer;
|
|
38
|
+
/** Throws if a replayed frame of this window differed from a build of it, or if gpuix's own
|
|
39
|
+
* check found an element judged unchanged that built differently, and prints the counters when
|
|
40
|
+
* GPUIX_RETAIN_VERIFY_REPORT is set. The gpuix findings are read from the window's list, not
|
|
41
|
+
* from `retainMismatches`, which is one count per thread that only `resetBuildCounters` clears. */
|
|
42
|
+
function reportRetentionMismatches(native) {
|
|
43
|
+
const counters = native.getRetainedCounters();
|
|
44
|
+
const findings = native.getRetainMismatches();
|
|
45
|
+
if (process.env.GPUIX_RETAIN_VERIFY_REPORT !== undefined) {
|
|
46
|
+
console.log(`[retain-verify] verified=${counters.verified} mismatches=${counters.mismatches} gpuix-mismatches=${findings.length} replays=${counters.replays} ` +
|
|
47
|
+
`hits=${counters.hits} builds=${counters.builds} lazy=${counters.lazyBuilds} non-reusable=${counters.nonReusable}`);
|
|
48
|
+
}
|
|
49
|
+
if (counters.mismatches > 0) {
|
|
50
|
+
throw new Error(`${counters.mismatches} replayed frames differed from a build:\n${native.getRetentionMismatches().join('\n')}`);
|
|
51
|
+
}
|
|
52
|
+
if (findings.length > 0) {
|
|
53
|
+
throw new Error(`${findings.length} elements were judged unchanged and built differently:\n${findings.join('\n')}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export class TestRenderer {
|
|
57
|
+
/** Native TestGpuixRenderer — all state lives here in Rust's RetainedTree. */
|
|
58
|
+
native;
|
|
59
|
+
applyBatch;
|
|
60
|
+
focusNext;
|
|
61
|
+
focusPrevious;
|
|
62
|
+
promptForPaths;
|
|
63
|
+
/** The test window's own clipboard. It never touches the system's. */
|
|
64
|
+
writeClipboardText;
|
|
65
|
+
readClipboardText;
|
|
66
|
+
setWindowSelectionChange;
|
|
67
|
+
setWindowAppearanceChange;
|
|
68
|
+
setWindowMediaChange;
|
|
69
|
+
getMediaFeatures;
|
|
70
|
+
setWindowKeyEvents;
|
|
71
|
+
setWindowEventListeners;
|
|
72
|
+
dispatchEvent;
|
|
73
|
+
dispatchSyncEvent;
|
|
74
|
+
onError;
|
|
75
|
+
constructor(options = {}) {
|
|
76
|
+
if (!NativeTestRenderer) {
|
|
77
|
+
throw new Error("TestGpuixRenderer is macOS and Windows only. Linux builds have no test-support because wgpu cannot read a rendered image back yet. GpuixRenderer still works on Linux.");
|
|
78
|
+
}
|
|
79
|
+
const fonts = readFontFiles(options.fonts);
|
|
80
|
+
this.native = new NativeTestRenderer(options, options.scaleFactor);
|
|
81
|
+
if (latestRenderer) {
|
|
82
|
+
// The native side keeps one window per thread and this constructor replaced it, so the
|
|
83
|
+
// earlier renderer is as dead as a disposed one: it would reach the new window's state, and
|
|
84
|
+
// throw once that is disposed.
|
|
85
|
+
forgetWindowSource(latestRenderer);
|
|
86
|
+
unregisterKeyframesHost(latestRenderer.native);
|
|
87
|
+
closeWebAnimations(latestRenderer);
|
|
88
|
+
}
|
|
89
|
+
latestRenderer = this;
|
|
90
|
+
for (const font of fonts)
|
|
91
|
+
this.native.registerFont(font);
|
|
92
|
+
registerKeyframesHost(this.native);
|
|
93
|
+
this.applyBatch = this.native.applyBatch.bind(this.native);
|
|
94
|
+
this.focusNext = this.native.focusNext.bind(this.native);
|
|
95
|
+
this.focusPrevious = this.native.focusPrevious.bind(this.native);
|
|
96
|
+
this.promptForPaths = this.native.promptForPaths.bind(this.native);
|
|
97
|
+
this.writeClipboardText = this.native.writeClipboardText.bind(this.native);
|
|
98
|
+
this.readClipboardText = this.native.readClipboardText.bind(this.native);
|
|
99
|
+
this.setWindowKeyEvents = this.native.setWindowKeyEvents.bind(this.native);
|
|
100
|
+
this.setWindowEventListeners = this.native.setWindowEventListeners.bind(this.native);
|
|
101
|
+
this.setWindowSelectionChange = this.native.setWindowSelectionChange.bind(this.native);
|
|
102
|
+
this.setWindowAppearanceChange = this.native.setWindowAppearanceChange.bind(this.native);
|
|
103
|
+
this.onError = options.onError;
|
|
104
|
+
this.setWindowMediaChange = this.native.setWindowMediaChange.bind(this.native);
|
|
105
|
+
this.getMediaFeatures = this.native.getMediaFeatures.bind(this.native);
|
|
106
|
+
this.dispatchEvent = options.dispatchEvent ?? ((event) => {
|
|
107
|
+
createRendererState(this).dispatch(event);
|
|
108
|
+
});
|
|
109
|
+
const state = createRendererState(this);
|
|
110
|
+
state.setErrorHandler(options.onError);
|
|
111
|
+
this.dispatchSyncEvent = reportSyncErrors(options.dispatchSyncEvent ?? ((event) => state.dispatchSync(event)), (error) => state.reportError(error));
|
|
112
|
+
this.native.setSyncEventHandler((event) => this.dispatchSyncEvent(event));
|
|
113
|
+
bindClipboard(this);
|
|
114
|
+
}
|
|
115
|
+
/** Releases the sync event handler, which captures this renderer, and tears the native
|
|
116
|
+
* window down. Call it after the root is unmounted. Native calls that need the window
|
|
117
|
+
* throw afterwards; the retained tree stays readable. Without it the window is leaked
|
|
118
|
+
* when the thread exits. */
|
|
119
|
+
dispose() {
|
|
120
|
+
// A run under GPUIX_RETAIN_VERIFY fails at the window that showed a replayed frame that a
|
|
121
|
+
// build would have shown differently, and says which test's window it was.
|
|
122
|
+
if (process.env.GPUIX_RETAIN_VERIFY !== undefined)
|
|
123
|
+
reportRetentionMismatches(this.native);
|
|
124
|
+
forgetElementSource(this);
|
|
125
|
+
unregisterKeyframesHost(this.native);
|
|
126
|
+
closeWebAnimations(this);
|
|
127
|
+
this.native.setSyncEventHandler(null);
|
|
128
|
+
this.native.dispose();
|
|
129
|
+
}
|
|
130
|
+
// ── GPUI pipeline methods ───────────────────────────────────────
|
|
131
|
+
/** Trigger the real GPUI rendering pipeline (GpuixView::render() →
|
|
132
|
+
* build_element() → apply_styles() → layout). */
|
|
133
|
+
flush() {
|
|
134
|
+
this.native.flush();
|
|
135
|
+
}
|
|
136
|
+
/** Drain events collected by the native GPUI event handlers. */
|
|
137
|
+
drainEvents() {
|
|
138
|
+
return this.native.drainEvents();
|
|
139
|
+
}
|
|
140
|
+
// ── Frame loop ──────────────────────────────────────────────────
|
|
141
|
+
// The surface `startFrameLoop` drives, so the wake path can be checked without a live window.
|
|
142
|
+
// The test window draws no frames: a tick delivers the input AppKit has queued for it.
|
|
143
|
+
/** Wakes `callback` when the main run loop has work, as `GpuixRenderer.setFrameWakeCallback`
|
|
144
|
+
* does. Returns true on macOS. Pass null to clear. */
|
|
145
|
+
setFrameWakeCallback(callback) {
|
|
146
|
+
return this.native.setFrameWakeCallback(callback);
|
|
147
|
+
}
|
|
148
|
+
/** Whether `startFrameLoop` must tick this renderer: true on macOS. */
|
|
149
|
+
requiresTick() {
|
|
150
|
+
return this.native.requiresTick();
|
|
151
|
+
}
|
|
152
|
+
/** One frame-loop step: hands the input AppKit queued for the test window (see
|
|
153
|
+
* `postNativeEvent`) to it, then to the JS handlers, as `dispatchNativeEvents` does. Draws
|
|
154
|
+
* nothing: frames stay `flush` and `drawFrame`. */
|
|
155
|
+
tick() {
|
|
156
|
+
const running = this.native.tick();
|
|
157
|
+
this.dispatchNativeEvents();
|
|
158
|
+
return running;
|
|
159
|
+
}
|
|
160
|
+
/** Posts a real NSEvent for the test window from `com.apple.NSEventThread`, the way AppKit
|
|
161
|
+
* receives input, so an idle `startFrameLoop(testRenderer)` is woken by it. `kind` is
|
|
162
|
+
* `"keyDown"` (the key `a`), `"mouseMove"`, `"click"` or `"scroll"` (3 lines down); the last
|
|
163
|
+
* three need `x` and `y` in logical pixels from the window's top left. The window is neither
|
|
164
|
+
* made key nor shown, and nothing reaches the OS. `tick()` delivers the event. */
|
|
165
|
+
postNativeEvent(kind, x, y) {
|
|
166
|
+
this.native.postNativeEvent(kind, x, y);
|
|
167
|
+
}
|
|
168
|
+
// ── Native end-to-end simulation ────────────────────────────────
|
|
169
|
+
// These methods go through the full GPUI pipeline:
|
|
170
|
+
// native simulate → GPUI dispatch → hit test → event handler →
|
|
171
|
+
// emit_event_full → drainEvents → framework event handler
|
|
172
|
+
/** Drain events from the native GPUI pipeline and feed them into the
|
|
173
|
+
* framework event registry, triggering state updates synchronously.
|
|
174
|
+
* Loops until no more events are produced — handles re-entrant events
|
|
175
|
+
* that may be generated during framework state updates. */
|
|
176
|
+
dispatchNativeEvents() {
|
|
177
|
+
// The probe draws, and its reports are what the loop below drains.
|
|
178
|
+
settleObservations(this);
|
|
179
|
+
for (;;) {
|
|
180
|
+
const events = this.native.drainEvents();
|
|
181
|
+
if (events.length === 0)
|
|
182
|
+
break;
|
|
183
|
+
for (const event of events) {
|
|
184
|
+
try {
|
|
185
|
+
this.dispatchEvent(event);
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
if (!this.onError)
|
|
189
|
+
throw error;
|
|
190
|
+
this.onError(error);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// Nothing else would run the observers' batch in a synchronous test.
|
|
195
|
+
flushObservations(this);
|
|
196
|
+
}
|
|
197
|
+
/** Press keys on whatever currently holds focus, in Playwright's
|
|
198
|
+
* `keyboard.press` grammar. A press is a DOM `key` value (`"Enter"`, `"a"`,
|
|
199
|
+
* `"A"`, `"ArrowDown"`, `"Escape"`, `"Tab"`, `"F2"`) or a `code` name
|
|
200
|
+
* (`"Space"`, `"KeyA"`, `"Digit1"`), with modifiers joined by `+`
|
|
201
|
+
* (`"Shift+Tab"`, `"Meta+a"`, `"Alt+ArrowLeft"`, `"ControlOrMeta+Shift+P"`;
|
|
202
|
+
* `ControlOrMeta` is Meta on macOS). `keystrokes` is a sequence of presses
|
|
203
|
+
* separated by spaces: `"a Shift+b Enter"`. Each press is a `keydown` then a
|
|
204
|
+
* `keyup`, and a typed key inserts its character into a focused input.
|
|
205
|
+
* Names are case-sensitive and an unknown one throws before anything is
|
|
206
|
+
* pressed.
|
|
207
|
+
*
|
|
208
|
+
* Unlike `nativeSimulateKeystrokes`, this focuses nothing first, which is
|
|
209
|
+
* the only way to test that `autoFocus` (or a click) actually moved focus. */
|
|
210
|
+
simulateKeystrokes(keystrokes) {
|
|
211
|
+
this.native.flushIfDirty();
|
|
212
|
+
// Events already queued reach JS before a key does, as on a live window.
|
|
213
|
+
// Delivered after it, they would undo what the key's own synchronous focus
|
|
214
|
+
// events did.
|
|
215
|
+
this.dispatchNativeEvents();
|
|
216
|
+
this.native.simulateKeystrokes(keystrokes);
|
|
217
|
+
this.dispatchNativeEvents();
|
|
218
|
+
this.native.flushIfDirty();
|
|
219
|
+
}
|
|
220
|
+
/** Focus an element, then press keys on it (the `simulateKeystrokes` grammar).
|
|
221
|
+
* @param elementId - element to focus
|
|
222
|
+
* @param keystrokes - space-separated presses, e.g. `"h i Enter Shift+Tab"` */
|
|
223
|
+
nativeSimulateKeystrokes(elementId, keystrokes) {
|
|
224
|
+
this.native.flushIfDirty();
|
|
225
|
+
this.native.focusElement(elementId);
|
|
226
|
+
// Focus events are queued but a key is delivered synchronously: let the
|
|
227
|
+
// focus handlers run first, as in a browser.
|
|
228
|
+
this.dispatchNativeEvents();
|
|
229
|
+
this.native.simulateKeystrokes(keystrokes);
|
|
230
|
+
this.dispatchNativeEvents();
|
|
231
|
+
}
|
|
232
|
+
/** Focus an element, then send one `keydown` for one press (the
|
|
233
|
+
* `simulateKeystrokes` grammar), with no `keyup` after it. Pairs with
|
|
234
|
+
* `nativeSimulateKeyUp`.
|
|
235
|
+
* @param elementId - element to focus
|
|
236
|
+
* @param keystroke - one press, e.g. `"a"`, `"Enter"`, `"Meta+s"`
|
|
237
|
+
* @param isHeld - whether this is a key-repeat event (default: false)
|
|
238
|
+
*/
|
|
239
|
+
nativeSimulateKeyDown(elementId, keystroke, isHeld) {
|
|
240
|
+
this.native.flushIfDirty();
|
|
241
|
+
this.native.focusElement(elementId);
|
|
242
|
+
// Focus events are queued but a key is delivered synchronously: let the
|
|
243
|
+
// focus handlers run first, as in a browser.
|
|
244
|
+
this.dispatchNativeEvents();
|
|
245
|
+
this.native.simulateKeyDown(keystroke, isHeld);
|
|
246
|
+
this.dispatchNativeEvents();
|
|
247
|
+
}
|
|
248
|
+
/** Focus an element, then send one `keyup` for one press (the
|
|
249
|
+
* `simulateKeystrokes` grammar). Pairs with `nativeSimulateKeyDown`.
|
|
250
|
+
* @param elementId - element to focus
|
|
251
|
+
* @param keystroke - one press, e.g. `"a"`, `"Enter"`, `"Meta+s"`
|
|
252
|
+
*/
|
|
253
|
+
nativeSimulateKeyUp(elementId, keystroke) {
|
|
254
|
+
this.native.flushIfDirty();
|
|
255
|
+
this.native.focusElement(elementId);
|
|
256
|
+
// Focus events are queued but a key is delivered synchronously: let the
|
|
257
|
+
// focus handlers run first, as in a browser.
|
|
258
|
+
this.dispatchNativeEvents();
|
|
259
|
+
this.native.simulateKeyUp(keystroke);
|
|
260
|
+
this.dispatchNativeEvents();
|
|
261
|
+
}
|
|
262
|
+
/** Focus a text input, then run an input method composition in it, one step
|
|
263
|
+
* per key. Each step is what macOS does for a key the input method takes: a
|
|
264
|
+
* `keydown` with `key` `"Process"`, the composition events and the `change`
|
|
265
|
+
* of the edit, then the `keyup` of that key. A text input is the only element
|
|
266
|
+
* that composes, so `elementId` must be one.
|
|
267
|
+
* @param elementId - the `<input>` or `<textarea>` to compose in
|
|
268
|
+
* @param steps - the keys the input method consumes, in order */
|
|
269
|
+
simulateComposition(elementId, steps) {
|
|
270
|
+
this.native.flushIfDirty();
|
|
271
|
+
this.native.focusElement(elementId);
|
|
272
|
+
// Focus events are queued but a key is delivered synchronously: let the
|
|
273
|
+
// focus handlers run first, as in a browser.
|
|
274
|
+
this.dispatchNativeEvents();
|
|
275
|
+
for (const step of steps) {
|
|
276
|
+
this.native.simulateImeKeyDown(step.key, step.action, step.action === "unmark" ? undefined : step.text);
|
|
277
|
+
// The edit's events are queued behind the keydown, and a browser fires
|
|
278
|
+
// them before the keyup.
|
|
279
|
+
this.dispatchNativeEvents();
|
|
280
|
+
this.native.simulateKeyUp(step.key);
|
|
281
|
+
this.dispatchNativeEvents();
|
|
282
|
+
}
|
|
283
|
+
this.native.flushIfDirty();
|
|
284
|
+
}
|
|
285
|
+
/** End-to-end: simulate a click through GPUI hit testing →
|
|
286
|
+
* dispatch resulting events to the framework. */
|
|
287
|
+
nativeSimulateClick(x, y, button, modifiers) {
|
|
288
|
+
this.native.flushIfDirty();
|
|
289
|
+
// Two inputs, not `native.simulateClick`: whatever the press queued must
|
|
290
|
+
// reach JS before the release, as it does when a person lets go later.
|
|
291
|
+
this.native.simulateMouseDown(x, y, button ?? 0, modifiers);
|
|
292
|
+
this.dispatchNativeEvents();
|
|
293
|
+
this.native.simulateMouseUp(x, y, button ?? 0, modifiers);
|
|
294
|
+
this.dispatchNativeEvents();
|
|
295
|
+
// Flush again after framework state updates so the Rust RetainedTree
|
|
296
|
+
// is fully rebuilt and GPUI has re-laid-out before any screenshot.
|
|
297
|
+
this.native.flushIfDirty();
|
|
298
|
+
}
|
|
299
|
+
/** End-to-end: simulate scroll wheel through GPUI →
|
|
300
|
+
* dispatch resulting events to the framework. Deltas have the DOM's sign, like
|
|
301
|
+
* `WheelEvent.deltaY`: positive `deltaY` scrolls down (the content moves up),
|
|
302
|
+
* positive `deltaX` scrolls right. */
|
|
303
|
+
nativeSimulateScrollWheel(x, y, deltaX, deltaY, modifiers, phase) {
|
|
304
|
+
this.native.flushIfDirty();
|
|
305
|
+
this.native.simulateScrollWheel(x, y, deltaX, deltaY, modifiers, phase);
|
|
306
|
+
this.dispatchNativeEvents();
|
|
307
|
+
}
|
|
308
|
+
/** Dispatch a wheel without the surrounding flushes, for perf sampling. Deltas
|
|
309
|
+
* have the DOM's sign: positive `deltaY` scrolls down.
|
|
310
|
+
* Call `flush()` yourself, or the sample is the framework update only and
|
|
311
|
+
* none of the GPUI build, layout and paint that follows. */
|
|
312
|
+
dispatchScrollWheel(x, y, deltaX, deltaY, modifiers, phase) {
|
|
313
|
+
this.native.simulateScrollWheel(x, y, deltaX, deltaY, modifiers, phase);
|
|
314
|
+
this.dispatchNativeEvents();
|
|
315
|
+
}
|
|
316
|
+
/** Dispatch a move without the surrounding flushes, for perf sampling.
|
|
317
|
+
* `nativeSimulateMouseMove` draws a pending change before and after the move, so a
|
|
318
|
+
* drag timed with it includes the paint and a wheel timed with `dispatchScrollWheel`
|
|
319
|
+
* does not. */
|
|
320
|
+
dispatchMouseMove(x, y, modifiers, movement) {
|
|
321
|
+
this.native.simulateMouseMove(x, y, modifiers, movement?.x, movement?.y);
|
|
322
|
+
this.dispatchNativeEvents();
|
|
323
|
+
}
|
|
324
|
+
/** End-to-end: simulate mouse move through GPUI →
|
|
325
|
+
* dispatch resulting events to the framework. The move reports the buttons held by
|
|
326
|
+
* `nativeSimulateMouseDown` and not yet released by `nativeSimulateMouseUp`, as a real
|
|
327
|
+
* OS does on every move, so a drag is a down, moves, an up.
|
|
328
|
+
* @param movement - what the platform reports as this move's motion, which becomes
|
|
329
|
+
* `movementX`/`movementY`. A real platform reports the delta from the last position;
|
|
330
|
+
* the simulation reports `{ x: 0, y: 0 }` unless told otherwise. */
|
|
331
|
+
nativeSimulateMouseMove(x, y, modifiers, movement) {
|
|
332
|
+
this.native.flushIfDirty();
|
|
333
|
+
this.native.simulateMouseMove(x, y, modifiers, movement?.x, movement?.y);
|
|
334
|
+
this.dispatchNativeEvents();
|
|
335
|
+
// Flush again after framework state updates so hover styles are applied
|
|
336
|
+
// and the Rust tree is current before any screenshot.
|
|
337
|
+
this.native.flushIfDirty();
|
|
338
|
+
}
|
|
339
|
+
/** End-to-end: the pointer leaves the window at (x, y), the way the platform
|
|
340
|
+
* reports it, with no mouse move after the last position inside. */
|
|
341
|
+
nativeSimulateMouseExit(x, y) {
|
|
342
|
+
this.native.flushIfDirty();
|
|
343
|
+
this.native.simulateMouseExit(x, y);
|
|
344
|
+
this.dispatchNativeEvents();
|
|
345
|
+
this.native.flushIfDirty();
|
|
346
|
+
}
|
|
347
|
+
/** The OS ate the release of `button` (0=left, the default, 1=middle, 2=right): the
|
|
348
|
+
* renderer forgets it as held and dispatches nothing. The next move reports no button,
|
|
349
|
+
* which is how a page sees a release it never got (the pointer let go outside the
|
|
350
|
+
* window), and the press is cancelled. */
|
|
351
|
+
simulateLostMouseUp(button) {
|
|
352
|
+
this.native.simulateLostMouseUp(button);
|
|
353
|
+
}
|
|
354
|
+
/** End-to-end: simulate mouse down through GPUI hit testing →
|
|
355
|
+
* dispatch resulting events to the framework.
|
|
356
|
+
* @param button - 0=left (default), 1=middle, 2=right
|
|
357
|
+
* @param clickCount - the platform's count: 2 for the second press of a double click */
|
|
358
|
+
nativeSimulateMouseDown(x, y, button, modifiers, clickCount) {
|
|
359
|
+
this.native.flushIfDirty();
|
|
360
|
+
this.native.simulateMouseDown(x, y, button ?? 0, modifiers, clickCount);
|
|
361
|
+
this.dispatchNativeEvents();
|
|
362
|
+
this.native.flushIfDirty();
|
|
363
|
+
}
|
|
364
|
+
/** End-to-end: simulate mouse up through GPUI hit testing →
|
|
365
|
+
* dispatch resulting events to the framework.
|
|
366
|
+
* @param button - 0=left (default), 1=middle, 2=right
|
|
367
|
+
* @param clickCount - the platform's count: 2 for the release of a double click */
|
|
368
|
+
nativeSimulateMouseUp(x, y, button, modifiers, clickCount) {
|
|
369
|
+
this.native.flushIfDirty();
|
|
370
|
+
this.native.simulateMouseUp(x, y, button ?? 0, modifiers, clickCount);
|
|
371
|
+
this.dispatchNativeEvents();
|
|
372
|
+
this.native.flushIfDirty();
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* End-to-end: files from the OS enter the window at `x`, `y`. Returns the operation the
|
|
376
|
+
* window answered the OS drag with, as macOS would see it: the `dropEffect` the `dragover`
|
|
377
|
+
* ended with (`"none"` when no handler cancelled it), or `null` when native picked none.
|
|
378
|
+
*/
|
|
379
|
+
nativeSimulateFileDragEnter(x, y, paths) {
|
|
380
|
+
this.native.flushIfDirty();
|
|
381
|
+
const operation = this.native.simulateFileDragEnter(x, y, paths);
|
|
382
|
+
this.dispatchNativeEvents();
|
|
383
|
+
this.native.flushIfDirty();
|
|
384
|
+
return operation;
|
|
385
|
+
}
|
|
386
|
+
/** End-to-end: the dragged files move to `x`, `y`. Returns the operation, as `nativeSimulateFileDragEnter` does. */
|
|
387
|
+
nativeSimulateFileDragMove(x, y) {
|
|
388
|
+
this.native.flushIfDirty();
|
|
389
|
+
const operation = this.native.simulateFileDragMove(x, y);
|
|
390
|
+
this.dispatchNativeEvents();
|
|
391
|
+
this.native.flushIfDirty();
|
|
392
|
+
return operation;
|
|
393
|
+
}
|
|
394
|
+
/** End-to-end: the dragged files are released at `x`, `y`. */
|
|
395
|
+
nativeSimulateFileDragSubmit(x, y) {
|
|
396
|
+
this.native.flushIfDirty();
|
|
397
|
+
this.native.simulateFileDragSubmit(x, y);
|
|
398
|
+
this.dispatchNativeEvents();
|
|
399
|
+
this.native.flushIfDirty();
|
|
400
|
+
}
|
|
401
|
+
/** End-to-end: the dragged files leave the window. */
|
|
402
|
+
nativeSimulateFileDragExit() {
|
|
403
|
+
this.native.flushIfDirty();
|
|
404
|
+
this.native.simulateFileDragExit();
|
|
405
|
+
this.dispatchNativeEvents();
|
|
406
|
+
this.native.flushIfDirty();
|
|
407
|
+
}
|
|
408
|
+
/** End-to-end: the platform's drag session ends. */
|
|
409
|
+
nativeSimulateFileDragEnd() {
|
|
410
|
+
this.native.flushIfDirty();
|
|
411
|
+
this.native.simulateFileDragEnd();
|
|
412
|
+
this.dispatchNativeEvents();
|
|
413
|
+
this.native.flushIfDirty();
|
|
414
|
+
}
|
|
415
|
+
/** End-to-end: a whole Finder-style drop at `x`, `y`, as the platform sends it: enter, submit, exit, end. */
|
|
416
|
+
nativeSimulateFileDrop(x, y, paths) {
|
|
417
|
+
this.native.flushIfDirty();
|
|
418
|
+
this.native.simulateFileDrop(x, y, paths);
|
|
419
|
+
this.dispatchNativeEvents();
|
|
420
|
+
this.native.flushIfDirty();
|
|
421
|
+
}
|
|
422
|
+
// ── Tree inspection (queries Rust RetainedTree via napi) ────────
|
|
423
|
+
/** Build a flat map of TestElements from the native tree JSON.
|
|
424
|
+
* One FFI call to get the full tree, then parse into TestElement objects. */
|
|
425
|
+
buildElementMap() {
|
|
426
|
+
const json = JSON.parse(this.native.getTreeJson());
|
|
427
|
+
const map = new Map();
|
|
428
|
+
const walk = (node, parentId) => {
|
|
429
|
+
if (!node)
|
|
430
|
+
return;
|
|
431
|
+
map.set(node.id, {
|
|
432
|
+
id: node.id,
|
|
433
|
+
type: node.type,
|
|
434
|
+
style: node.style ?? {},
|
|
435
|
+
text: node.text ?? null,
|
|
436
|
+
events: new Set(node.events ?? []),
|
|
437
|
+
children: (node.children ?? []).map((c) => c.id),
|
|
438
|
+
parentId,
|
|
439
|
+
...(node.testId ? { testId: node.testId } : {}),
|
|
440
|
+
...(node.customProps ? { customProps: node.customProps } : {}),
|
|
441
|
+
});
|
|
442
|
+
for (const child of node.children ?? []) {
|
|
443
|
+
walk(child, node.id);
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
walk(json, null);
|
|
447
|
+
return map;
|
|
448
|
+
}
|
|
449
|
+
/** Get the root element. */
|
|
450
|
+
getRoot() {
|
|
451
|
+
const rootId = this.native.getRootId();
|
|
452
|
+
if (rootId == null)
|
|
453
|
+
return undefined;
|
|
454
|
+
return this.buildElementMap().get(rootId);
|
|
455
|
+
}
|
|
456
|
+
/** Get an element by ID. */
|
|
457
|
+
getElement(id) {
|
|
458
|
+
return this.buildElementMap().get(id);
|
|
459
|
+
}
|
|
460
|
+
/** Find elements by type (e.g. "div", "text"). */
|
|
461
|
+
findByType(type) {
|
|
462
|
+
return [...this.buildElementMap().values()].filter((el) => el.type === type);
|
|
463
|
+
}
|
|
464
|
+
/** Find the first text element containing the given string. */
|
|
465
|
+
findByText(text) {
|
|
466
|
+
const wanted = matchableText(text);
|
|
467
|
+
return [...this.buildElementMap().values()].find((el) => el.text != null && matchableText(el.text).includes(wanted));
|
|
468
|
+
}
|
|
469
|
+
findByTestId(testId) {
|
|
470
|
+
return [...this.buildElementMap().values()].find((el) => el.testId === testId);
|
|
471
|
+
}
|
|
472
|
+
/** Get all text content in the tree (depth-first). */
|
|
473
|
+
getAllText() {
|
|
474
|
+
return this.native.getAllText();
|
|
475
|
+
}
|
|
476
|
+
/** Print the tree structure for debugging. Only includes non-empty fields. */
|
|
477
|
+
toJSON() {
|
|
478
|
+
return JSON.parse(this.native.getTreeJson());
|
|
479
|
+
}
|
|
480
|
+
/** GPUI accessibility dump from the last painted frame. */
|
|
481
|
+
getA11yTree() {
|
|
482
|
+
return JSON.parse(this.native.getA11yTree());
|
|
483
|
+
}
|
|
484
|
+
getAutomationTree() {
|
|
485
|
+
return this.native.getAutomationTree();
|
|
486
|
+
}
|
|
487
|
+
/** Every element the native tree holds, reachable or not. `toJSON()` walks
|
|
488
|
+
* from the root, so only this can see a node that was detached and leaked. */
|
|
489
|
+
getRetainedElementCount() {
|
|
490
|
+
return this.native.getRetainedElementCount();
|
|
491
|
+
}
|
|
492
|
+
/** What the element build has done since the last `resetBuildCounters()`:
|
|
493
|
+
* elements built and `StyleDesc` / `Inherited` clones. A draw builds every
|
|
494
|
+
* element, so divide by the frames drawn for the per-frame figure. */
|
|
495
|
+
getBuildCounters() {
|
|
496
|
+
return this.native.getBuildCounters();
|
|
497
|
+
}
|
|
498
|
+
/** What the retained subtrees did since the window was made: subtrees not built, replayed,
|
|
499
|
+
* built late, and frames compared with a whole build under `setRetainVerify`. */
|
|
500
|
+
getRetainedCounters() {
|
|
501
|
+
return this.native.getRetainedCounters();
|
|
502
|
+
}
|
|
503
|
+
/** What building and drawing the elements did since the window was made: elements prepainted and
|
|
504
|
+
* painted (those in a culled subtree apart), and the scene primitives painting inserted against
|
|
505
|
+
* the ones a replay inserted again. A replayed subtree prepaints and paints none of its
|
|
506
|
+
* elements; its replays are `getRetainedCounters().replays`. */
|
|
507
|
+
getFrameWorkCounters() {
|
|
508
|
+
return this.native.getFrameWorkCounters();
|
|
509
|
+
}
|
|
510
|
+
/** How the frames compared under `setRetainVerify` differed from a whole build. */
|
|
511
|
+
getRetentionMismatches() {
|
|
512
|
+
return this.native.getRetentionMismatches();
|
|
513
|
+
}
|
|
514
|
+
/** Layout nodes made, found again and freed, styles and child lists set, text measured and
|
|
515
|
+
* shaped, and the subtrees whose extent and overflow were walked again, by the window's layout
|
|
516
|
+
* engine since the window was made. */
|
|
517
|
+
getLayoutCounters() {
|
|
518
|
+
return this.native.getLayoutCounters();
|
|
519
|
+
}
|
|
520
|
+
resetBuildCounters() {
|
|
521
|
+
this.native.resetBuildCounters();
|
|
522
|
+
}
|
|
523
|
+
/** Hashes what every element builds, and counts in `retainMismatches` an element the
|
|
524
|
+
* retain key judged unchanged that built differently since the last build. Costs a
|
|
525
|
+
* formatted style per element: leave it off to time a frame. */
|
|
526
|
+
setRetainVerify(verify) {
|
|
527
|
+
this.native.setRetainVerify(verify);
|
|
528
|
+
}
|
|
529
|
+
/** Turns the skipping of unchanged subtrees on or off (on by default): off, every frame
|
|
530
|
+
* builds every element, the way `GPUIX_RETAIN_OFF` makes it for the whole run. */
|
|
531
|
+
setRetainPlanning(planning) {
|
|
532
|
+
this.native.setRetainPlanning(planning);
|
|
533
|
+
}
|
|
534
|
+
/** Whether a frame records what it builds at all (on by default): off, a frame costs what it did
|
|
535
|
+
* before retention, as `GPUIX_RETAIN_OFF` makes it for the whole run. Turning it on starts
|
|
536
|
+
* from a frame that builds everything. */
|
|
537
|
+
setRetainEnabled(enabled) {
|
|
538
|
+
this.native.setRetainEnabled(enabled);
|
|
539
|
+
}
|
|
540
|
+
/** Whether a tick of animations that only change what they paint is drawn by painting the roots
|
|
541
|
+
* they change again in the frame before it (on by default, as `GPUIX_PATCH_OFF` turns off for the
|
|
542
|
+
* whole run). It is never patched while the retention is off. */
|
|
543
|
+
setPatchEnabled(enabled) {
|
|
544
|
+
this.native.setPatchEnabled(enabled);
|
|
545
|
+
}
|
|
546
|
+
/** How ticks were drawn by patching since the window was made: the frames and roots patched, the
|
|
547
|
+
* ticks gpui drew whole after the patch was tried (`declines`, by reason, as JSON) and the ones
|
|
548
|
+
* gpuix did not try to patch (`unpatchable`, by kind, as JSON). */
|
|
549
|
+
getPatchCounters() {
|
|
550
|
+
return this.native.getPatchCounters();
|
|
551
|
+
}
|
|
552
|
+
/** Counts as if these exclusions (`svg`, `overflow-hidden`, `custom-element`, ...) were lifted,
|
|
553
|
+
* to see which are worth lifting first. Nothing is reused. */
|
|
554
|
+
setRetainLift(names) {
|
|
555
|
+
this.native.setRetainLift(names);
|
|
556
|
+
}
|
|
557
|
+
/** The elements counted in `retainMismatches`, one line each. */
|
|
558
|
+
getRetainMismatches() {
|
|
559
|
+
return this.native.getRetainMismatches();
|
|
560
|
+
}
|
|
561
|
+
/** Frames the window has painted. A test compares it before and after to
|
|
562
|
+
* count the draws a read caused: a bounds or scroll read paints nothing. */
|
|
563
|
+
getPaintedFrameCount() {
|
|
564
|
+
return this.native.getPaintedFrameCount();
|
|
565
|
+
}
|
|
566
|
+
/** Draw a dirty window when an update or a simulated input flushes (the
|
|
567
|
+
* default), or leave that to `drawFrame`, as a real window leaves it to its
|
|
568
|
+
* display link. A test that counts draws per frame turns it off. */
|
|
569
|
+
setInlineDraws(enabled) {
|
|
570
|
+
this.native.setInlineDraws(enabled);
|
|
571
|
+
}
|
|
572
|
+
/** One display-link frame: deliver the queued frame callbacks, then draw a
|
|
573
|
+
* pending change once. Pairs with `setInlineDraws(false)`. */
|
|
574
|
+
drawFrame() {
|
|
575
|
+
this.native.drawFrame();
|
|
576
|
+
}
|
|
577
|
+
/** Layout-only passes the window has run. A read of bounds or scroll metrics
|
|
578
|
+
* on a dirty window is one, in place of a draw, and the next frame draws
|
|
579
|
+
* once regardless. */
|
|
580
|
+
getLayoutPassCount() {
|
|
581
|
+
return this.native.getLayoutPassCount();
|
|
582
|
+
}
|
|
583
|
+
/** Pretends a screen reader is connected, as the test window starts out,
|
|
584
|
+
* or not. Without one no accessibility tree is built. */
|
|
585
|
+
setA11yActive(active) {
|
|
586
|
+
this.native.setA11yActive(active);
|
|
587
|
+
}
|
|
588
|
+
/** Subtrees the window has skipped as lying outside their clip, over its
|
|
589
|
+
* life. Compare before and after a frame. */
|
|
590
|
+
getCulledSubtreeCount() {
|
|
591
|
+
return this.native.getCulledSubtreeCount();
|
|
592
|
+
}
|
|
593
|
+
getElementBounds(elementId) {
|
|
594
|
+
flushPendingMutations(this);
|
|
595
|
+
return this.native.getElementBounds(elementId);
|
|
596
|
+
}
|
|
597
|
+
/** `getElementBounds` for the first report of an observer: it lays out as that does, but not the content
|
|
598
|
+
* of a skipped `content-visibility` ancestor, and an element inside it has no box (null). */
|
|
599
|
+
probeElementBounds(elementId) {
|
|
600
|
+
flushPendingMutations(this);
|
|
601
|
+
return this.native.probeElementBounds(elementId);
|
|
602
|
+
}
|
|
603
|
+
/** The box the last layout gave an element, or null if it was not built. Unlike `getElementBounds`
|
|
604
|
+
* it does not lay out the content of a skipped `content-visibility` ancestor to answer, so it
|
|
605
|
+
* tells whether that content is built. */
|
|
606
|
+
getBuiltElementBounds(elementId) {
|
|
607
|
+
flushPendingMutations(this);
|
|
608
|
+
return this.native.getBuiltElementBounds(elementId);
|
|
609
|
+
}
|
|
610
|
+
/** Ids of the elements under the window point, topmost first. */
|
|
611
|
+
getElementsAt(x, y) {
|
|
612
|
+
flushPendingMutations(this);
|
|
613
|
+
return this.native.getElementsAt(x, y);
|
|
614
|
+
}
|
|
615
|
+
clockPause() {
|
|
616
|
+
return this.native.clockPause();
|
|
617
|
+
}
|
|
618
|
+
clockSet(nowMs) {
|
|
619
|
+
return this.native.clockSet(nowMs);
|
|
620
|
+
}
|
|
621
|
+
clockFastForward(deltaMs) {
|
|
622
|
+
return this.native.clockFastForward(deltaMs);
|
|
623
|
+
}
|
|
624
|
+
/** `clockFastForward` without telling the window, as time passing does in a live one: the frame
|
|
625
|
+
* after it is a tick, which patches the frame before it when only paint-only animations ran. */
|
|
626
|
+
clockAdvance(deltaMs) {
|
|
627
|
+
return this.native.clockAdvance(deltaMs);
|
|
628
|
+
}
|
|
629
|
+
clockResume() {
|
|
630
|
+
return this.native.clockResume();
|
|
631
|
+
}
|
|
632
|
+
// ── Motion values ───────────────────────────────────────────────
|
|
633
|
+
createMotionValue(...args) {
|
|
634
|
+
this.native.createMotionValue(...args);
|
|
635
|
+
}
|
|
636
|
+
createMotionTransform(...args) {
|
|
637
|
+
this.native.createMotionTransform(...args);
|
|
638
|
+
}
|
|
639
|
+
createMotionFollower(...args) {
|
|
640
|
+
this.native.createMotionFollower(...args);
|
|
641
|
+
}
|
|
642
|
+
createMotionSpring(...args) {
|
|
643
|
+
this.native.createMotionSpring(...args);
|
|
644
|
+
}
|
|
645
|
+
disposeMotionValue(id) {
|
|
646
|
+
return this.native.disposeMotionValue(id);
|
|
647
|
+
}
|
|
648
|
+
getMotionValue(id) {
|
|
649
|
+
return this.native.getMotionValue(id);
|
|
650
|
+
}
|
|
651
|
+
getMotionValueVelocity(id) {
|
|
652
|
+
return this.native.getMotionValueVelocity(id);
|
|
653
|
+
}
|
|
654
|
+
isMotionValueAnimating(id) {
|
|
655
|
+
return this.native.isMotionValueAnimating(id);
|
|
656
|
+
}
|
|
657
|
+
setMotionValue(id, value) {
|
|
658
|
+
this.native.setMotionValue(id, value);
|
|
659
|
+
}
|
|
660
|
+
jumpMotionValue(id, value) {
|
|
661
|
+
this.native.jumpMotionValue(id, value);
|
|
662
|
+
}
|
|
663
|
+
animateMotionValue(id, target, transition) {
|
|
664
|
+
this.native.animateMotionValue(id, target, transition);
|
|
665
|
+
}
|
|
666
|
+
stopMotionValue(id) {
|
|
667
|
+
this.native.stopMotionValue(id);
|
|
668
|
+
}
|
|
669
|
+
pauseMotionValue(id) {
|
|
670
|
+
this.native.pauseMotionValue(id);
|
|
671
|
+
}
|
|
672
|
+
playMotionValue(id) {
|
|
673
|
+
this.native.playMotionValue(id);
|
|
674
|
+
}
|
|
675
|
+
seekMotionValue(id, seconds) {
|
|
676
|
+
this.native.seekMotionValue(id, seconds);
|
|
677
|
+
}
|
|
678
|
+
setMotionValueSpeed(id, speed) {
|
|
679
|
+
this.native.setMotionValueSpeed(id, speed);
|
|
680
|
+
}
|
|
681
|
+
completeMotionValue(id) {
|
|
682
|
+
this.native.completeMotionValue(id);
|
|
683
|
+
}
|
|
684
|
+
detachMotionValueClock(id) {
|
|
685
|
+
this.native.detachMotionValueClock(id);
|
|
686
|
+
}
|
|
687
|
+
getMotionValuePlayback(id) {
|
|
688
|
+
return this.native.getMotionValuePlayback(id);
|
|
689
|
+
}
|
|
690
|
+
setMotionValueListener(id, change, animation) {
|
|
691
|
+
this.native.setMotionValueListener(id, change, animation);
|
|
692
|
+
}
|
|
693
|
+
/** Advance GPUI's test dispatcher and run due timers.
|
|
694
|
+
* This is not `clockFastForward`. That moves the motion clock only.
|
|
695
|
+
* Use this for caret blink, input drag autoscroll, and list edge scroll. */
|
|
696
|
+
advanceTime(milliseconds) {
|
|
697
|
+
this.native.advanceTime(milliseconds);
|
|
698
|
+
this.dispatchNativeEvents();
|
|
699
|
+
}
|
|
700
|
+
focusElement(elementId) {
|
|
701
|
+
this.native.flushIfDirty();
|
|
702
|
+
this.native.focusElement(elementId);
|
|
703
|
+
this.dispatchNativeEvents();
|
|
704
|
+
}
|
|
705
|
+
blurElement(elementId) {
|
|
706
|
+
this.native.flushIfDirty();
|
|
707
|
+
this.native.blurElement(elementId);
|
|
708
|
+
this.dispatchNativeEvents();
|
|
709
|
+
}
|
|
710
|
+
matchesFocus(elementId, selector) {
|
|
711
|
+
this.native.flushIfDirty();
|
|
712
|
+
const matches = this.native.matchesFocus(elementId, selector);
|
|
713
|
+
this.dispatchNativeEvents();
|
|
714
|
+
return matches;
|
|
715
|
+
}
|
|
716
|
+
getFocusedElementId() {
|
|
717
|
+
this.native.flushIfDirty();
|
|
718
|
+
return this.native.getFocusedElementId();
|
|
719
|
+
}
|
|
720
|
+
/** Makes the offscreen window key (`true`, the default) or not, as when the user switches apps. */
|
|
721
|
+
setWindowActive(active) {
|
|
722
|
+
this.native.setWindowActive(active);
|
|
723
|
+
this.native.flushIfDirty();
|
|
724
|
+
this.dispatchNativeEvents();
|
|
725
|
+
}
|
|
726
|
+
focusNextWithin(elementId) {
|
|
727
|
+
this.native.flushIfDirty();
|
|
728
|
+
this.native.focusNextWithin(elementId);
|
|
729
|
+
this.dispatchNativeEvents();
|
|
730
|
+
}
|
|
731
|
+
focusPreviousWithin(elementId) {
|
|
732
|
+
this.native.flushIfDirty();
|
|
733
|
+
this.native.focusPreviousWithin(elementId);
|
|
734
|
+
this.dispatchNativeEvents();
|
|
735
|
+
}
|
|
736
|
+
/** The offscreen window size, so `useWindowSize()` works under test. */
|
|
737
|
+
getWindowSize() {
|
|
738
|
+
return this.native.getWindowSize();
|
|
739
|
+
}
|
|
740
|
+
/** `"system"` | `"light"` | `"dark"`, as on the live renderer: the window's appearance
|
|
741
|
+
* follows (`getAppearance`, `light-dark()`, `prefers-color-scheme`) and `appearanceChange`
|
|
742
|
+
* is delivered. `"system"` returns to the platform's own. */
|
|
743
|
+
setAppearance(appearance) {
|
|
744
|
+
this.native.flush();
|
|
745
|
+
this.native.setAppearance(appearance);
|
|
746
|
+
this.native.flush();
|
|
747
|
+
this.dispatchNativeEvents();
|
|
748
|
+
}
|
|
749
|
+
/** `"overlay"` (macOS "Show scroll bars" Automatically or When scrolling, which the test
|
|
750
|
+
* window starts with) or `"classic"` (Always, which macOS also picks while a mouse is
|
|
751
|
+
* attached). Classic bars are always drawn and take 15px (11 with `scrollbarWidth: "thin"`)
|
|
752
|
+
* out of the container, so its `clientWidth` and content box shrink by it. The test window
|
|
753
|
+
* never hears from the OS, so this is how a test flips the setting. */
|
|
754
|
+
setScrollbarStyle(style) {
|
|
755
|
+
this.native.flush();
|
|
756
|
+
this.native.setScrollbarStyle(style);
|
|
757
|
+
this.native.flush();
|
|
758
|
+
}
|
|
759
|
+
/** The window's title, as the platform window has it: the `title` option, or `"GPUIX"`. */
|
|
760
|
+
getWindowTitle() {
|
|
761
|
+
this.native.flush();
|
|
762
|
+
return this.native.getWindowTitle();
|
|
763
|
+
}
|
|
764
|
+
/** The window's effective appearance, `"light"` or `"dark"`. */
|
|
765
|
+
getAppearance() {
|
|
766
|
+
this.native.flush();
|
|
767
|
+
return this.native.getAppearance();
|
|
768
|
+
}
|
|
769
|
+
/** Set the OS accessibility preferences this window reports (all start as
|
|
770
|
+
* "no preference") and deliver the resulting `mediaChange` event. The test
|
|
771
|
+
* window never hears from the OS, so this is how a test flips reduced motion. */
|
|
772
|
+
setMediaFeatures(features) {
|
|
773
|
+
this.native.setMediaFeatures(features.reducedMotion, features.increasedContrast, features.forcedColors);
|
|
774
|
+
this.dispatchNativeEvents();
|
|
775
|
+
}
|
|
776
|
+
/** Resize the offscreen window's content, as dragging its edge would: `getWindowSize()`
|
|
777
|
+
* and the size queries of `matchMedia` see the new size, and their `change` events are
|
|
778
|
+
* delivered. */
|
|
779
|
+
setWindowSize(width, height) {
|
|
780
|
+
this.native.setWindowSize(width, height);
|
|
781
|
+
this.dispatchNativeEvents();
|
|
782
|
+
}
|
|
783
|
+
/** Give the offscreen window the scale factor of a display of another density (`2` is a
|
|
784
|
+
* retina one), as a window dragged to it has: the next frame lays out and paints at it, and
|
|
785
|
+
* a screenshot is the window's size times it. The factor stays until it is set again:
|
|
786
|
+
* resizing the window does not reset it. The window starts at 1 (`scaleFactor` option). */
|
|
787
|
+
setWindowScaleFactor(scaleFactor) {
|
|
788
|
+
this.native.setWindowScaleFactor(scaleFactor);
|
|
789
|
+
}
|
|
790
|
+
// ── Scroll API ──────────────────────────────────────────────────
|
|
791
|
+
/** Scroll an element (overflow: "scroll", or a `<virtual-list>`) to a DOM scroll
|
|
792
|
+
* position: `left` and `top` are `scrollLeft` and `scrollTop`, zero or more, so
|
|
793
|
+
* scrolling down is a larger `top`. Like a browser's, the write forces a layout only if
|
|
794
|
+
* one is pending, and the window draws the new offset with its next frame. */
|
|
795
|
+
scrollTo(elementId, left, top, behavior) {
|
|
796
|
+
this.native.scrollTo(elementId, left, top, behavior);
|
|
797
|
+
}
|
|
798
|
+
/** Scroll a child into view by its index in the children list.
|
|
799
|
+
*
|
|
800
|
+
* `offsetInItem` is in pixels. A negative value anchors the viewport top
|
|
801
|
+
* above the item, resolved against measured row heights at layout time, so
|
|
802
|
+
* a row stays pixel-stable while unmeasured rows are spliced in above it. */
|
|
803
|
+
scrollToItem(elementId, index, offsetInItem) {
|
|
804
|
+
this.native.flush();
|
|
805
|
+
this.native.scrollToItem(elementId, index, offsetInItem);
|
|
806
|
+
this.dispatchNativeEvents();
|
|
807
|
+
this.native.flush();
|
|
808
|
+
}
|
|
809
|
+
/** The DOM's `scrollIntoView`: every scrollable ancestor scrolls until the element sits
|
|
810
|
+
* where `block` (default `"start"`) and `inline` (default `"nearest"`) ask. */
|
|
811
|
+
scrollIntoView(elementId, block, inline, behavior) {
|
|
812
|
+
this.native.flush();
|
|
813
|
+
this.native.scrollIntoView(elementId, block, inline, behavior);
|
|
814
|
+
this.dispatchNativeEvents();
|
|
815
|
+
this.native.flush();
|
|
816
|
+
}
|
|
817
|
+
/** Applies from the next simulated pointer input. See `PointerCaptureMethods`. */
|
|
818
|
+
setPointerCapture(elementId, pointerId) {
|
|
819
|
+
this.native.setPointerCapture(elementId, pointerId);
|
|
820
|
+
}
|
|
821
|
+
releasePointerCapture(elementId, pointerId) {
|
|
822
|
+
this.native.releasePointerCapture(elementId, pointerId);
|
|
823
|
+
}
|
|
824
|
+
hasPointerCapture(elementId, pointerId) {
|
|
825
|
+
return this.native.hasPointerCapture(elementId, pointerId);
|
|
826
|
+
}
|
|
827
|
+
clickElement(elementId) {
|
|
828
|
+
this.native.clickElement(elementId);
|
|
829
|
+
}
|
|
830
|
+
getAnimations(elementId) {
|
|
831
|
+
return this.native.getAnimations(elementId);
|
|
832
|
+
}
|
|
833
|
+
getSubtreeAnimations(elementId) {
|
|
834
|
+
return this.native.getSubtreeAnimations(elementId);
|
|
835
|
+
}
|
|
836
|
+
getDocumentAnimations() {
|
|
837
|
+
return this.native.getDocumentAnimations();
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* The XML the `<svg>` painted from its shape children in the last frame, with
|
|
841
|
+
* animated attributes as that frame had them, or `null` when it painted none.
|
|
842
|
+
*/
|
|
843
|
+
getSvgMarkup(elementId) {
|
|
844
|
+
return this.native.getSvgMarkup(elementId);
|
|
845
|
+
}
|
|
846
|
+
getAnimation(animationId) {
|
|
847
|
+
return this.native.getAnimation(animationId);
|
|
848
|
+
}
|
|
849
|
+
releaseAnimation(animationId) {
|
|
850
|
+
this.native.releaseAnimation(animationId);
|
|
851
|
+
}
|
|
852
|
+
forgetAnimation(animationId) {
|
|
853
|
+
this.native.forgetAnimation(animationId);
|
|
854
|
+
}
|
|
855
|
+
getAnimationEffect(animationId) {
|
|
856
|
+
return this.native.getAnimationEffect(animationId);
|
|
857
|
+
}
|
|
858
|
+
getAnimationKeyframes(animationId) {
|
|
859
|
+
return this.native.getAnimationKeyframes(animationId);
|
|
860
|
+
}
|
|
861
|
+
updateAnimationTiming(animationId, timing) {
|
|
862
|
+
this.native.updateAnimationTiming(animationId, timing);
|
|
863
|
+
}
|
|
864
|
+
pauseAnimation(animationId) {
|
|
865
|
+
this.native.pauseAnimation(animationId);
|
|
866
|
+
}
|
|
867
|
+
playAnimation(animationId) {
|
|
868
|
+
this.native.playAnimation(animationId);
|
|
869
|
+
}
|
|
870
|
+
setAnimationCurrentTime(animationId, milliseconds) {
|
|
871
|
+
this.native.setAnimationCurrentTime(animationId, milliseconds);
|
|
872
|
+
}
|
|
873
|
+
setAnimationPlaybackRate(animationId, rate) {
|
|
874
|
+
this.native.setAnimationPlaybackRate(animationId, rate);
|
|
875
|
+
}
|
|
876
|
+
cancelAnimation(animationId) {
|
|
877
|
+
this.native.cancelAnimation(animationId);
|
|
878
|
+
}
|
|
879
|
+
finishAnimation(animationId) {
|
|
880
|
+
this.native.finishAnimation(animationId);
|
|
881
|
+
}
|
|
882
|
+
reverseAnimation(animationId) {
|
|
883
|
+
this.native.reverseAnimation(animationId);
|
|
884
|
+
}
|
|
885
|
+
updateAnimationPlaybackRate(animationId, rate) {
|
|
886
|
+
this.native.updateAnimationPlaybackRate(animationId, rate);
|
|
887
|
+
}
|
|
888
|
+
setAnimationStartTime(animationId, startTime) {
|
|
889
|
+
this.native.setAnimationStartTime(animationId, startTime);
|
|
890
|
+
}
|
|
891
|
+
getAnimationTimelineTime() {
|
|
892
|
+
return this.native.getAnimationTimelineTime();
|
|
893
|
+
}
|
|
894
|
+
/** Applies from the next simulated pointer input. */
|
|
895
|
+
cancelDrag() {
|
|
896
|
+
this.native.cancelDrag();
|
|
897
|
+
}
|
|
898
|
+
/** The frame it asks for is drawn here, so `pointerlockchange` or
|
|
899
|
+
* `pointerlockerror` has reached its listeners when this returns. */
|
|
900
|
+
requestPointerLock(elementId) {
|
|
901
|
+
this.native.requestPointerLock(elementId);
|
|
902
|
+
this.dispatchNativeEvents();
|
|
903
|
+
}
|
|
904
|
+
exitPointerLock() {
|
|
905
|
+
this.native.exitPointerLock();
|
|
906
|
+
this.dispatchNativeEvents();
|
|
907
|
+
}
|
|
908
|
+
pointerLockElement() {
|
|
909
|
+
return this.native.pointerLockElement();
|
|
910
|
+
}
|
|
911
|
+
/** Whether the simulated platform grants lock requests. The offscreen test
|
|
912
|
+
* window is never key, so a real GPUI lock would always be refused; this
|
|
913
|
+
* stands in for a platform that can lock, and off, for one that refuses. */
|
|
914
|
+
simulatePointerLockGranting(granting) {
|
|
915
|
+
this.native.simulatePointerLockGranting(granting);
|
|
916
|
+
}
|
|
917
|
+
/** The platform drops the lock, as when the window deactivates. */
|
|
918
|
+
simulatePointerLockLost() {
|
|
919
|
+
this.native.simulatePointerLockLost();
|
|
920
|
+
this.dispatchNativeEvents();
|
|
921
|
+
}
|
|
922
|
+
/** What the window was asked to do by presses in `appRegion: "drag"` boxes since the last
|
|
923
|
+
* call, oldest first: `"move"`, `"titlebarDoubleClick"` (macOS), `"zoom"` or `"showMenu"`
|
|
924
|
+
* (Linux). The offscreen window cannot start a real move, so the test window records
|
|
925
|
+
* instead. Every test window is frameless, so `appRegion` always applies. */
|
|
926
|
+
takeSimulatedWindowActions() {
|
|
927
|
+
return this.native.takeSimulatedWindowActions();
|
|
928
|
+
}
|
|
929
|
+
/** The default action of a scrolling key. See `scrollByKey` on the native renderer. */
|
|
930
|
+
scrollByKey(key, shift) {
|
|
931
|
+
this.native.flush();
|
|
932
|
+
this.native.scrollByKey(key, shift);
|
|
933
|
+
this.dispatchNativeEvents();
|
|
934
|
+
this.native.flush();
|
|
935
|
+
}
|
|
936
|
+
/** `[scrollLeft, scrollTop, scrollWidth, scrollHeight, clientWidth, clientHeight]`, or
|
|
937
|
+
* null for an element that has not been painted. */
|
|
938
|
+
getScrollMetrics(elementId) {
|
|
939
|
+
return this.native.getScrollMetrics(elementId);
|
|
940
|
+
}
|
|
941
|
+
setImage(elementId, bytes) {
|
|
942
|
+
this.native.flush();
|
|
943
|
+
this.native.setImage(elementId, bytes);
|
|
944
|
+
this.native.flush();
|
|
945
|
+
}
|
|
946
|
+
setStyleImage(url, bytes) {
|
|
947
|
+
this.native.flush();
|
|
948
|
+
this.native.setStyleImage(url, bytes);
|
|
949
|
+
this.native.flush();
|
|
950
|
+
}
|
|
951
|
+
failStyleImage(url, message) {
|
|
952
|
+
this.native.flush();
|
|
953
|
+
this.native.failStyleImage(url, message);
|
|
954
|
+
this.native.flush();
|
|
955
|
+
}
|
|
956
|
+
selectText(elementId) {
|
|
957
|
+
this.native.flush();
|
|
958
|
+
this.native.selectText(elementId);
|
|
959
|
+
this.native.flush();
|
|
960
|
+
}
|
|
961
|
+
setSelectionRange(elementId, start, end, direction) {
|
|
962
|
+
this.native.flush();
|
|
963
|
+
this.native.setSelectionRange(elementId, start, end, direction);
|
|
964
|
+
this.native.flush();
|
|
965
|
+
}
|
|
966
|
+
getTextSelection(elementId) {
|
|
967
|
+
this.native.flush();
|
|
968
|
+
return this.native.getTextSelection(elementId);
|
|
969
|
+
}
|
|
970
|
+
requestSubmit(elementId) {
|
|
971
|
+
this.native.flushIfDirty();
|
|
972
|
+
this.native.requestSubmit(elementId);
|
|
973
|
+
this.dispatchNativeEvents();
|
|
974
|
+
this.native.flushIfDirty();
|
|
975
|
+
}
|
|
976
|
+
registerFont(bytes) {
|
|
977
|
+
this.native.registerFont(bytes);
|
|
978
|
+
this.native.flush();
|
|
979
|
+
}
|
|
980
|
+
// ── <webview> ───────────────────────────────────────────────────
|
|
981
|
+
// The page of a webview is made when the element is first painted, so these draw first.
|
|
982
|
+
// The engine is a scripted page: it answers from what these set, and reports what they make it.
|
|
983
|
+
webviewCommand(elementId, command, argument) {
|
|
984
|
+
this.native.flush();
|
|
985
|
+
this.native.webviewCommand(elementId, command, argument);
|
|
986
|
+
}
|
|
987
|
+
webviewState(elementId) {
|
|
988
|
+
this.native.flush();
|
|
989
|
+
return this.native.webviewState(elementId);
|
|
990
|
+
}
|
|
991
|
+
webviewExecuteJavascript(elementId, code) {
|
|
992
|
+
this.native.flush();
|
|
993
|
+
return this.native.webviewExecuteJavascript(elementId, code);
|
|
994
|
+
}
|
|
995
|
+
/** What the page of the `<webview>` was asked to do, in order. */
|
|
996
|
+
getWebviewCommands(elementId) {
|
|
997
|
+
this.native.flush();
|
|
998
|
+
return this.native.getWebviewCommands(elementId);
|
|
999
|
+
}
|
|
1000
|
+
/** The page starts loading: `did-start-loading`. */
|
|
1001
|
+
simulateWebviewStartLoading(elementId) {
|
|
1002
|
+
this.native.flush();
|
|
1003
|
+
this.native.simulateWebviewStartLoading(elementId);
|
|
1004
|
+
this.dispatchNativeEvents();
|
|
1005
|
+
}
|
|
1006
|
+
/** The page stops loading: `did-stop-loading`. */
|
|
1007
|
+
simulateWebviewStopLoading(elementId) {
|
|
1008
|
+
this.native.flush();
|
|
1009
|
+
this.native.simulateWebviewStopLoading(elementId);
|
|
1010
|
+
this.dispatchNativeEvents();
|
|
1011
|
+
}
|
|
1012
|
+
/** The page commits `url`: `did-navigate`, and `getURL()` answers it. */
|
|
1013
|
+
simulateWebviewNavigate(elementId, url) {
|
|
1014
|
+
this.native.flush();
|
|
1015
|
+
this.native.simulateWebviewNavigate(elementId, url);
|
|
1016
|
+
this.dispatchNativeEvents();
|
|
1017
|
+
}
|
|
1018
|
+
/** The page hands the keyboard on, as it does when Tab leaves its last tabbable element
|
|
1019
|
+
* (`backwards`: Shift+Tab leaves its first): focus moves to the next (previous) tab stop. */
|
|
1020
|
+
simulateWebviewTabOut(elementId, backwards = false) {
|
|
1021
|
+
this.native.flush();
|
|
1022
|
+
this.native.simulateWebviewTabOut(elementId, backwards);
|
|
1023
|
+
this.dispatchNativeEvents();
|
|
1024
|
+
}
|
|
1025
|
+
/** The page gets a title: `page-title-updated`, and `getTitle()` answers it. */
|
|
1026
|
+
simulateWebviewTitle(elementId, title, explicitSet = true) {
|
|
1027
|
+
this.native.flush();
|
|
1028
|
+
this.native.simulateWebviewTitle(elementId, title, explicitSet);
|
|
1029
|
+
this.dispatchNativeEvents();
|
|
1030
|
+
}
|
|
1031
|
+
/** A load fails: `did-fail-load`, with an `NSURLError` code. */
|
|
1032
|
+
simulateWebviewFail(elementId, errorCode, errorDescription, validatedUrl) {
|
|
1033
|
+
this.native.flush();
|
|
1034
|
+
this.native.simulateWebviewFail(elementId, errorCode, errorDescription, validatedUrl);
|
|
1035
|
+
this.dispatchNativeEvents();
|
|
1036
|
+
}
|
|
1037
|
+
/** What `canGoBack()` and `canGoForward()` answer. */
|
|
1038
|
+
setWebviewHistory(elementId, canGoBack, canGoForward) {
|
|
1039
|
+
this.native.flush();
|
|
1040
|
+
this.native.setWebviewHistory(elementId, canGoBack, canGoForward);
|
|
1041
|
+
}
|
|
1042
|
+
/** Settles the oldest `executeJavaScript` of the `<webview>` that waits: resolves it with
|
|
1043
|
+
* `result` (anything JSON holds; `undefined` for `undefined`), or rejects it with `error`. */
|
|
1044
|
+
settleWebviewScript(elementId, outcome) {
|
|
1045
|
+
this.native.flush();
|
|
1046
|
+
this.native.settleWebviewScript(elementId, outcome.result === undefined ? undefined : JSON.stringify(outcome.result), outcome.error);
|
|
1047
|
+
}
|
|
1048
|
+
/** Where the last frame put the surface of the `<webview>`; null while it has none. */
|
|
1049
|
+
getWebviewSurface(elementId) {
|
|
1050
|
+
this.native.flush();
|
|
1051
|
+
return this.native.getWebviewSurface(elementId);
|
|
1052
|
+
}
|
|
1053
|
+
/** The layers of the last composed frame, bottom to top. */
|
|
1054
|
+
getCompositionLayers() {
|
|
1055
|
+
this.native.flush();
|
|
1056
|
+
return this.native.getCompositionLayers();
|
|
1057
|
+
}
|
|
1058
|
+
/** The views AppKit holds for the `<webview>`, with the placement the last frame applied. */
|
|
1059
|
+
getWebviewNativeView(elementId) {
|
|
1060
|
+
this.native.flush();
|
|
1061
|
+
return this.native.getWebviewNativeView(elementId);
|
|
1062
|
+
}
|
|
1063
|
+
// ── <nativeview> ────────────────────────────────────────────────
|
|
1064
|
+
// The surface of a nativeview is made when its element is first painted.
|
|
1065
|
+
/** Where the last frame put the native view of the `<nativeview>`; null while it has no
|
|
1066
|
+
* surface or the frame did not place it (`display: none`, `visibility: hidden`, culled). */
|
|
1067
|
+
getNativeviewSurface(elementId) {
|
|
1068
|
+
this.native.flush();
|
|
1069
|
+
return this.native.getNativeviewSurface(elementId);
|
|
1070
|
+
}
|
|
1071
|
+
/** The native surfaces the `<nativeview>` elements made and dropped, in order. `"removed"` is
|
|
1072
|
+
* when the element drops the surface (after `detach`); its view leaves the window with the
|
|
1073
|
+
* next frame. */
|
|
1074
|
+
getNativeSurfaceEvents() {
|
|
1075
|
+
this.native.flush();
|
|
1076
|
+
return this.native.getNativeSurfaceEvents();
|
|
1077
|
+
}
|
|
1078
|
+
/** How many warnings the `<nativeview>` has issued: one when the platform cannot make its
|
|
1079
|
+
* surface, however many frames are drawn. */
|
|
1080
|
+
getNativeviewWarnings(elementId) {
|
|
1081
|
+
this.native.flush();
|
|
1082
|
+
return this.native.getNativeviewWarnings(elementId);
|
|
1083
|
+
}
|
|
1084
|
+
/** `false` makes the window refuse native surfaces, as a platform that cannot compose does:
|
|
1085
|
+
* call it before the first frame that paints a `<nativeview>`. */
|
|
1086
|
+
setNativeSurfacesAvailable(available) {
|
|
1087
|
+
this.native.flush();
|
|
1088
|
+
this.native.setNativeSurfacesAvailable(available);
|
|
1089
|
+
}
|
|
1090
|
+
setImagePixels(elementId, width, height, pixels, format) {
|
|
1091
|
+
this.native.flush();
|
|
1092
|
+
this.native.setImagePixels(elementId, width, height, pixels, format);
|
|
1093
|
+
this.native.flush();
|
|
1094
|
+
}
|
|
1095
|
+
/** The DOM scroll position `[scrollLeft, scrollTop]` (zero or more, like `element.scrollLeft`
|
|
1096
|
+
* and the `onScroll` event), or null if the element is not scrollable. */
|
|
1097
|
+
getScrollOffset(elementId) {
|
|
1098
|
+
this.native.flush();
|
|
1099
|
+
const result = this.native.getScrollOffset(elementId);
|
|
1100
|
+
if (!result)
|
|
1101
|
+
return null;
|
|
1102
|
+
return [result[0], result[1]];
|
|
1103
|
+
}
|
|
1104
|
+
/** The CSS keyword of the cursor the window asks the platform for where the pointer last
|
|
1105
|
+
* was (`"default"` when nothing asks). A mouse move updates it, a draw keeps it current.
|
|
1106
|
+
* Keywords that name one cursor read back as the first of them: `"pointer"`, `"grabbing"`,
|
|
1107
|
+
* `"nwse-resize"`, `"nesw-resize"`, `"not-allowed"`. */
|
|
1108
|
+
getCursor() {
|
|
1109
|
+
this.native.flush();
|
|
1110
|
+
return this.native.getCursor();
|
|
1111
|
+
}
|
|
1112
|
+
/** The logical scroll anchor of a `<virtual-list>`:
|
|
1113
|
+
* `[itemIndex, offsetInItemPx, viewportHeightPx]`, or null for anything
|
|
1114
|
+
* else. `itemIndex == item count` is gpui's at-end sentinel. Exact even
|
|
1115
|
+
* while row heights are still estimates, because it is the anchor gpui
|
|
1116
|
+
* itself scrolls by. */
|
|
1117
|
+
getListScrollTop(elementId) {
|
|
1118
|
+
this.native.flush();
|
|
1119
|
+
const result = this.native.getListScrollTop(elementId);
|
|
1120
|
+
if (!result)
|
|
1121
|
+
return null;
|
|
1122
|
+
return [result[0], result[1], result[2]];
|
|
1123
|
+
}
|
|
1124
|
+
// ── Selection API ───────────────────────────────────────────────
|
|
1125
|
+
/** Drag-select from (x1,y1) to (x2,y2) and return the selected text.
|
|
1126
|
+
*
|
|
1127
|
+
* Selection listeners are registered during **paint**, so the native helper
|
|
1128
|
+
* flushes between every step. Calling simulateMouseDown/Move/Up by hand
|
|
1129
|
+
* without those flushes selects nothing. */
|
|
1130
|
+
dragSelect(x1, y1, x2, y2) {
|
|
1131
|
+
this.native.dragSelect(x1, y1, x2, y2);
|
|
1132
|
+
return this.native.getSelectedText();
|
|
1133
|
+
}
|
|
1134
|
+
/** The current selection joined in document order, or null. */
|
|
1135
|
+
getSelectedText() {
|
|
1136
|
+
return this.native.getSelectedText();
|
|
1137
|
+
}
|
|
1138
|
+
/** Every string painted in the last frame, in paint order.
|
|
1139
|
+
*
|
|
1140
|
+
* `getAllText()` only sees `<text>` nodes in the retained tree. Native
|
|
1141
|
+
* elements like `<code>` and `<diff>` paint their text inside GPUI, so this
|
|
1142
|
+
* is the only way to assert on what they rendered. */
|
|
1143
|
+
getPaintedText() {
|
|
1144
|
+
return this.native.getPaintedText();
|
|
1145
|
+
}
|
|
1146
|
+
/** Every highlight wash painted in the last frame, in paint order.
|
|
1147
|
+
*
|
|
1148
|
+
* A quad never lands in `getPaintedText()`, and a soft-wrapped match must
|
|
1149
|
+
* draw one box per visual row, so each entry carries its `rects`. */
|
|
1150
|
+
getPaintedHighlights() {
|
|
1151
|
+
return this.native.getPaintedHighlights();
|
|
1152
|
+
}
|
|
1153
|
+
/** Syntax-cache counters as `[hits, misses, documents]`. */
|
|
1154
|
+
getSyntaxCacheStats() {
|
|
1155
|
+
const [hits, misses, documents] = this.native.getSyntaxCacheStats();
|
|
1156
|
+
return [hits, misses, documents];
|
|
1157
|
+
}
|
|
1158
|
+
clearSelection() {
|
|
1159
|
+
this.native.clearSelection();
|
|
1160
|
+
this.native.flush();
|
|
1161
|
+
}
|
|
1162
|
+
setDebugFrameOverlay(mode) {
|
|
1163
|
+
return this.native.setDebugFrameOverlay(mode);
|
|
1164
|
+
}
|
|
1165
|
+
getDebugFrameOverlay() {
|
|
1166
|
+
return this.native.getDebugFrameOverlay();
|
|
1167
|
+
}
|
|
1168
|
+
cycleDebugFrameOverlay() {
|
|
1169
|
+
return this.native.cycleDebugFrameOverlay();
|
|
1170
|
+
}
|
|
1171
|
+
resetDebugFrameOverlayStats() {
|
|
1172
|
+
this.native.resetDebugFrameOverlayStats();
|
|
1173
|
+
}
|
|
1174
|
+
getDebugFrameOverlayStats() {
|
|
1175
|
+
return this.native.getDebugFrameOverlayStats();
|
|
1176
|
+
}
|
|
1177
|
+
/** Capture the current Metal or DirectX frame and save it as a PNG. */
|
|
1178
|
+
captureScreenshot(path) {
|
|
1179
|
+
this.native.flush();
|
|
1180
|
+
this.native.captureScreenshot(path);
|
|
1181
|
+
}
|
|
1182
|
+
/** Whether the native GPUI test renderer is available. Always true. */
|
|
1183
|
+
get hasNative() {
|
|
1184
|
+
return true;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
//# sourceMappingURL=testing.js.map
|