perf(ioscompanion): paste long text for atomic landing

This commit is contained in:
pj committed 2026-06-07 00:28:40 +05:30
1 parent 2e817e3167
commit 841497cc30
2 files changed
+34 -8

No files matched your search

+4 -3
View File
@@ -217,9 +217,10 @@ func TestScreenshotRejectsNonPNG(t *testing.T) {
func TestInputTextFastPathSkipsFieldResolution(t *testing.T) { func TestInputTextFastPathSkipsFieldResolution(t *testing.T) {
companion := &fakeCompanion{accessibilityJSON: "[]"} companion := &fakeCompanion{accessibilityJSON: "[]"}
d := newTestDriver(companion) d := newTestDriver(companion)
// "hello" is fully mappable, so the fast keyboard path runs and never reads // "abc" is fully mappable and under the paste threshold, so the fast
// the accessibility dump for a field target. // keyboard path runs and never reads the accessibility dump for a field
if err := d.InputText(context.Background(), "hello"); err != nil { // target.
if err := d.InputText(context.Background(), "abc"); err != nil {
t.Fatalf("InputText: %v", err) t.Fatalf("InputText: %v", err)
} }
if indexOf(companion.calls, "hid") < 0 { if indexOf(companion.calls, "hid") < 0 {
+30 -5
View File
@@ -99,12 +99,28 @@ type fieldTarget struct {
centerY float64 centerY float64
} }
// inputText types text into the focused field. Mappable text goes through the // pasteLengthThreshold is the rune count above which mappable text still goes
// hardware keyboard in one HID stream; anything else falls back to the // through the pasteboard. Typed keys render progressively on the simulator
// pasteboard. The field target is only consulted on the pasteboard path. // (roughly 75ms per character), so a long typed string keeps the screen
// churning well past the HID call and stretches the post-action settle; a
// paste lands the whole value in one frame.
const pasteLengthThreshold = 3
// usesPasteboard reports whether text takes the pasteboard path: any
// unmappable rune forces it, and longer mappable text uses it so the value
// lands atomically.
func usesPasteboard(text string) bool {
_, skipped := typeString(text)
return len(skipped) > 0 || len([]rune(text)) > pasteLengthThreshold
}
// inputText types text into the focused field. Short mappable text goes
// through the hardware keyboard in one HID stream; everything else goes
// through the pasteboard. The field target is only consulted on the
// pasteboard path.
func inputText(ctx context.Context, run runner, text string, field fieldTarget) error { func inputText(ctx context.Context, run runner, text string, field fieldTarget) error {
presses, skipped := typeString(text) if !usesPasteboard(text) {
if len(skipped) == 0 { presses, _ := typeString(text)
return run.sendHID(ctx, keyPressEvents(presses)...) return run.sendHID(ctx, keyPressEvents(presses)...)
} }
return pasteText(ctx, run, text, field) return pasteText(ctx, run, text, field)
@@ -121,6 +137,15 @@ func pasteText(ctx context.Context, run runner, text string, field fieldTarget)
if err := run.sendHID(ctx, pasteChordEvents()...); err != nil { if err := run.sendHID(ctx, pasteChordEvents()...); err != nil {
return fmt.Errorf("send paste chord: %w", err) return fmt.Errorf("send paste chord: %w", err)
} }
// A warm paste lands within a frame or two; check once before paying
// the settle sleep so the common case stays fast.
quick, err := run.describeAll(ctx)
if err != nil {
return fmt.Errorf("describe accessibility: %w", err)
}
if pasteLanded(quick, field.identifier, text) {
return nil
}
if err := run.sleep(ctx, pasteSettle); err != nil { if err := run.sleep(ctx, pasteSettle); err != nil {
return err return err
} }