chore(autofill): mesurer ou passe le temps d'un remplissage

Un autofill lent apres une periode d'inactivite peut l'etre pour des
raisons sans rapport qui se ressemblent parfaitement de l'exterieur : le
moteur WebView2 qui se reveille, la prise du premier plan, ou l'attente
de la relache des modificateurs. Elles se corrigent a des endroits
opposes, donc on les separe au lieu de deviner laquelle accuser.

Trois marques : le temps que met la PAGE a repondre au raccourci, le
total, et le detail des deux attentes internes.

Premiere mesure : 13 ms de reponse apres 23 minutes en tray, total
635 ms. Le renderer ne dormait donc pas - l'hypothese la plus evidente
etait fausse - et 635 ms est le cout nominal de la sequence, faite de
pauses fixes (80 + 120 + 60 + 200 + 200). Le cas lent reste a attraper.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-09-02 19:31:03 +01:00
parent f8b7ffe379
commit da74a675be
2 changed files with 54 additions and 2 deletions
+19
View File
@@ -30,6 +30,7 @@ interface
uses uses
System.SysUtils, System.Classes, System.Math, System.Types, System.SysUtils, System.Classes, System.Math, System.Types,
System.Diagnostics,
FMX.Types, FMX.Forms, FMX.Types, FMX.Forms,
Winapi.Windows, Winapi.ShellAPI, Winapi.Messages, Winapi.MultiMon, Winapi.Windows, Winapi.ShellAPI, Winapi.Messages, Winapi.MultiMon,
PM.SingleInstance, PM.UserPrefs; PM.SingleInstance, PM.UserPrefs;
@@ -162,6 +163,8 @@ type
// settings_json : elle peut etre allumee coffre ferme, quand aucune session // settings_json : elle peut etre allumee coffre ferme, quand aucune session
// n'existe pour lire les reglages du serveur. // n'existe pour lire les reglages du serveur.
FLazyAlphaPct: Integer; FLazyAlphaPct: Integer;
// Voir la propriete LastFillWaits.
FLastFillWaits: string;
// Dernier etat connu du survol, pour ne reposer l'opacite que sur un // Dernier etat connu du survol, pour ne reposer l'opacite que sur un
// changement - SetLayeredWindowAttributes force un repaint a chaque appel. // changement - SetLayeredWindowAttributes force un repaint a chaque appel.
FLazyHover: Boolean; FLazyHover: Boolean;
@@ -250,6 +253,10 @@ type
function ExecuteAutofill(ATargetHWND: HWND; function ExecuteAutofill(ATargetHWND: HWND;
const AUsername, APassword: string; AUsernameOnly: Boolean = False; const AUsername, APassword: string; AUsernameOnly: Boolean = False;
ARestoreAfter: Boolean = False; AClearFirst: Boolean = True): Boolean; ARestoreAfter: Boolean = False; AClearFirst: Boolean = True): Boolean;
/// Le detail des deux attentes du dernier remplissage, pour le journal de
/// l'hote. Une chaine plutot qu'un evenement : ExecuteAutofill s'execute
/// sur le thread principal, juste avant que l'hote journalise.
property LastFillWaits: string read FLastFillWaits;
property SecureClipboard: TSecureClipboard read FSecureClipboard; property SecureClipboard: TSecureClipboard read FSecureClipboard;
property TrayAdded: Boolean read FTrayAdded; property TrayAdded: Boolean read FTrayAdded;
// Show or hide the lazy strip. Idempotent, and remembered across launches. // Show or hide the lazy strip. Idempotent, and remembered across launches.
@@ -1989,11 +1996,23 @@ begin
Sleep(MinimizeSettleMs); Sleep(MinimizeSettleMs);
end; end;
// LES DEUX SEULES ATTENTES QUI PEUVENT ENFLER, mesurees separement.
//
// Le total d'un remplissage est d'environ 660 ms par construction - des
// Sleep fixes. Quand il depasse plusieurs secondes, c'est forcement l'une de
// ces deux-la : la prise du premier plan, qui sonde jusqu'a 600 ms, ou
// l'attente de la relache des modificateurs, jusqu'a 1000 ms. Les confondre
// ferait chercher du cote du moteur web, qui n'y est pour rien.
var LSw := TStopwatch.StartNew;
if ATargetHWND <> 0 then if ATargetHWND <> 0 then
ForceForegroundWindow(ATargetHWND); ForceForegroundWindow(ATargetHWND);
var LFgMs := LSw.ElapsedMilliseconds;
LSw := TStopwatch.StartNew;
WaitForModifierRelease(1000); WaitForModifierRelease(1000);
ForceReleaseModifiers; // timeout hit with keys still down → clean state ForceReleaseModifiers; // timeout hit with keys still down → clean state
FLastFillWaits := Format('foreground=%d ms, modifiers=%d ms',
[LFgMs, LSw.ElapsedMilliseconds]);
Sleep(FocusSettleDelayMs); Sleep(FocusSettleDelayMs);
// Never type into our own window: if the target refused the foreground, // Never type into our own window: if the target refused the foreground,
+35 -2
View File
@@ -18,6 +18,7 @@ interface
uses uses
System.SysUtils, System.Classes, System.UITypes, System.NetEncoding, System.SysUtils, System.Classes, System.UITypes, System.NetEncoding,
System.StrUtils, System.Generics.Collections, System.IOUtils, System.JSON, System.StrUtils, System.Generics.Collections, System.IOUtils, System.JSON,
System.Diagnostics,
System.Net.HttpClient, System.Net.URLClient, System.Net.HttpClient, System.Net.URLClient,
Winapi.Windows, Winapi.ShellAPI, Winapi.Windows, Winapi.ShellAPI,
FMX.Forms, FMX.Controls, FMX.Controls.Presentation, FMX.StdCtrls, FMX.Forms, FMX.Controls, FMX.Controls.Presentation, FMX.StdCtrls,
@@ -109,6 +110,16 @@ type
FQuitting: Boolean; // set when user picks "Quit" in tray menu — bypasses FQuitting: Boolean; // set when user picks "Quit" in tray menu — bypasses
// FormCloseQuery's minimize-to-tray intercept. // FormCloseQuery's minimize-to-tray intercept.
FAutofillTargetHWND: HWND; // saved at hotkey time, consumed on /execute FAutofillTargetHWND: HWND; // saved at hotkey time, consumed on /execute
// CHRONOMETRE DU RACCOURCI, arme au moment ou l'on appelle la page.
//
// Un autofill lent apres une periode d'inactivite peut l'etre pour deux
// raisons sans rapport : le moteur WebView2 qui se reveille et met des
// secondes a repondre, ou la sequence de frappe elle-meme (prise du premier
// plan, attente des modificateurs). Les deux se ressemblent parfaitement de
// l'exterieur - l'utilisateur voit "ca a mis trois secondes" - et se
// corrigent a des endroits opposes. On mesure donc les deux segments
// separement plutot que de deviner lequel accuser.
FAutofillClock: TStopwatch;
// Pending payload for the 60ms delay timer (focus settle before SendInput). // Pending payload for the 60ms delay timer (focus settle before SendInput).
// Cleared inside AutofillTimerTick. // Cleared inside AutofillTimerTick.
FAutofillPendingHWND: HWND; FAutofillPendingHWND: HWND;
@@ -1153,6 +1164,16 @@ begin
FAutofillPendingNotify := GetParam('notify') <> '0'; // absent = ON FAutofillPendingNotify := GetParam('notify') <> '0'; // absent = ON
FAutofillTargetHWND := 0; FAutofillTargetHWND := 0;
// PREMIER SEGMENT : combien la PAGE a mis a repondre.
//
// Tout ce qui precede est du JavaScript - reveil du moteur, appariement du
// titre, dechiffrement du mot de passe. Si le chiffre est gros ici et petit
// plus bas, c'est le renderer qu'il faut regarder, pas la sequence de
// frappe. Le contraire designe SendInput et la prise du premier plan.
if FAutofillClock.IsRunning then
LogLine(Format('Autofill: page answered in %d ms',
[FAutofillClock.ElapsedMilliseconds]));
// Small timer so SetForegroundWindow has time to take effect before // Small timer so SetForegroundWindow has time to take effect before
// SendInput fires — avoids the first keystrokes going to our window. // SendInput fires — avoids the first keystrokes going to our window.
// TTimer.OnTimer is a TNotifyEvent (method, not anon proc) → we use a // TTimer.OnTimer is a TNotifyEvent (method, not anon proc) → we use a
@@ -1891,9 +1912,18 @@ begin
var LFillOk := FBridge.ExecuteAutofill(TargetHwnd, PendingUser, PendingPass, var LFillOk := FBridge.ExecuteAutofill(TargetHwnd, PendingUser, PendingPass,
UserOnly, not HideAfter, ClearFirst); UserOnly, not HideAfter, ClearFirst);
ForegroundAfter := GetForegroundWindow; ForegroundAfter := GetForegroundWindow;
LogLine(Format('Autofill executed — target=%s, foreground_after=%s, ok=%s', // SECOND SEGMENT, et le total. La difference entre les deux chiffres est le
// cout de la frappe : minimisation, prise du premier plan, attente de la
// relache des modificateurs, puis SendInput.
LogLine(Format('Autofill executed — target=%s, foreground_after=%s, ok=%s, ' +
'total=%d ms',
[IntToHex(TargetHwnd, 8), IntToHex(ForegroundAfter, 8), [IntToHex(TargetHwnd, 8), IntToHex(ForegroundAfter, 8),
BoolToStr(LFillOk, True)])); BoolToStr(LFillOk, True), FAutofillClock.ElapsedMilliseconds]));
FAutofillClock.Stop;
// Le detail des deux attentes internes : sans lui, un total de trois secondes
// ne dit pas LAQUELLE a enfle, et les deux se corrigent differemment.
if FBridge.LastFillWaits <> '' then
LogLine('Autofill waits — ' + FBridge.LastFillWaits);
// Tell JS whether the keystrokes were actually sent — the success toast // Tell JS whether the keystrokes were actually sent — the success toast
// must not lie when the target runs elevated (UIPI drops our input). // must not lie when the target runs elevated (UIPI drops our input).
WebBrowser.ExecuteJavaScript( WebBrowser.ExecuteJavaScript(
@@ -1942,6 +1972,9 @@ begin
else LKind := 'full'; else LKind := 'full';
end; end;
// Arme AVANT l'appel : ce qui suit se passe dans le moteur, et c'est
// justement ce temps-la qu'on cherche a voir.
FAutofillClock := TStopwatch.StartNew;
WebBrowser.ExecuteJavaScript( WebBrowser.ExecuteJavaScript(
'if(window.Bridge&&typeof Bridge.onAutofillRequest==="function")' + 'if(window.Bridge&&typeof Bridge.onAutofillRequest==="function")' +
'Bridge.onAutofillRequest("' + LTitle + '","' + LKind + '")'); 'Bridge.onAutofillRequest("' + LTitle + '","' + LKind + '")');