feat: tray quick-search + privacy hardening + race fixes
Quick search from tray - New "Quick search…" entry in the tray context menu (between Open and Lock vault). - Compact modal with live-filtered top-8 entries, arrow keys / Enter to copy the password (Shift+Enter copies the username instead), Esc to dismiss. Each row shows the favicon when cached. - Locked vault → focus the master password input instead of opening the modal (same pattern as the locked-autofill-hotkey path). - Window-state restore: Delphi remembers whether the window was hidden before the menu was opened and tells JS via the Bridge.openQuickSearch(wasHidden) arg. After the copy (or cancel) we hide back to the tray so the previously-foreground app comes back and Ctrl+V drops the password in. Tray notifications toggle - New Settings → Security "Show tray notifications" toggle. Gates Shell_NotifyIcon NIF_INFO balloons (currently only the "still running in the tray" first-time popup). Default ON, synced via settings_json so it follows the user across devices. - PM.Bridge.ShowNotifications exposed as a public property; JS pushes the value on every settings sync. Privacy: WebView2 phone-home killed - WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS set in the unit initialization section (before the TMS WebBrowser instantiates its CoreWebView2Environment). Disables: background networking, sync, component updates, breakpad/crashpad, domain reliability, client-side phishing detection, experiments, UMA upload, MediaRouter, OptimizationHints, SafeBrowsing enhanced, autofill server, privacy sandbox APIs. Verified via Resource Monitor: only 127.0.0.1 connections remain (plus DDG when favicons are on). Fixes - Blank-window-on-launch race: the 1.5 s navigation timer assumes WebView2 finishes init in time, but on slow machines Edge Chromium needs 2-3 s and the Navigate() call is silently dropped. WebBrowserInitialized now also navigates if a URL is still pending — first to run wins. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -12,7 +12,8 @@ uses
|
||||
FMX.TMSFNCTypes, FMX.TMSFNCUtils, FMX.TMSFNCGraphics, FMX.TMSFNCGraphicsTypes,
|
||||
FMX.TMSFNCCustomControl, FMX.TMSFNCWebBrowser,
|
||||
PM.HTTPServer, PM.Bridge, PM.QuickUnlock, PM.UserPrefs, PM.AutoStart,
|
||||
PM.Favicon;
|
||||
PM.Favicon,
|
||||
FMX.Platform.Win; // WindowHandleToPlatform → HWND for visibility check
|
||||
|
||||
type
|
||||
TMainForm = class(TForm)
|
||||
@@ -67,6 +68,7 @@ type
|
||||
ATargetHWND: HWND; const ATitle: string);
|
||||
procedure BridgeDebugHotkey;
|
||||
procedure BridgeNewEntryHotkey(const AWindowTitle: string);
|
||||
procedure BridgeQuickSearchRequest;
|
||||
procedure WebBrowserInitialized(Sender: TObject);
|
||||
end;
|
||||
|
||||
@@ -77,6 +79,10 @@ implementation
|
||||
|
||||
{$R *.fmx}
|
||||
|
||||
// Forward — used by WebBrowserInitialized (which sits above the actual
|
||||
// definition lower in the unit).
|
||||
function MaskAccessToken(const AUrl: string): string; forward;
|
||||
|
||||
procedure TMainForm.FormCreate(Sender: TObject);
|
||||
begin
|
||||
FServer := TPMHTTPServer.Create;
|
||||
@@ -90,6 +96,7 @@ begin
|
||||
FBridge.OnAutofillRequest := BridgeAutofillRequest;
|
||||
FBridge.OnDebugHotkey := BridgeDebugHotkey;
|
||||
FBridge.OnNewEntryHotkey := BridgeNewEntryHotkey;
|
||||
FBridge.OnQuickSearchRequest := BridgeQuickSearchRequest;
|
||||
FBridge.RegisterAutofillHotkey; // Ctrl+Shift+L active from startup
|
||||
FBridge.ApplyTitleBarTheme(True); // dark by default, JS may toggle later
|
||||
FAutofillTargetHWND := 0;
|
||||
@@ -158,6 +165,44 @@ procedure TMainForm.WebBrowserInitialized(Sender: TObject);
|
||||
begin
|
||||
WebBrowser.EnableContextMenu := False;
|
||||
WebBrowser.EnableShowDebugConsole := False;
|
||||
|
||||
// Race-safe navigation fallback: the 1.5 s timer in NavigateToVault
|
||||
// assumes WebView2 finishes its async init within that window. On slow
|
||||
// boots / cold-start machines Edge Chromium can take 2-3 s, and the
|
||||
// timer's Navigate() call lands while the browser is still uninitialised
|
||||
// → silently dropped → blank window forever. OnInitialized fires once
|
||||
// the engine is ready, so if a navigation is still pending here, do it
|
||||
// now. The timer either already ran (FPendingURL == '') or runs later
|
||||
// and no-ops on the empty string.
|
||||
FNavTimer.Enabled := False;
|
||||
if FPendingURL <> '' then
|
||||
begin
|
||||
LogLine('OnInitialized fallback nav to: ' + MaskAccessToken(FPendingURL));
|
||||
WebBrowser.Navigate(FPendingURL);
|
||||
FPendingURL := '';
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TMainForm.BridgeQuickSearchRequest;
|
||||
var
|
||||
LWasHidden: Boolean;
|
||||
LWasHiddenJs: string;
|
||||
begin
|
||||
// Tray menu "Quick search…" — bring the window back so the user can
|
||||
// see the modal, then ask JS to open it. JS handles the locked-vault
|
||||
// case (shows the auth screen with master-pw focused instead).
|
||||
if not FServer.Active then Exit;
|
||||
// Remember whether the window was hidden BEFORE we restore — after the
|
||||
// user picks an entry the JS layer asks us to minimize back so they can
|
||||
// paste into the target app without an extra alt-tab.
|
||||
LWasHidden := (not Self.Visible) or
|
||||
IsIconic(WindowHandleToPlatform(Self.Handle).Wnd);
|
||||
FBridge.RestoreFromTray;
|
||||
LWasHiddenJs := BoolToStr(LWasHidden, True).ToLower;
|
||||
WebBrowser.ExecuteJavaScript(
|
||||
'if(window.Bridge&&typeof Bridge.openQuickSearch==="function")' +
|
||||
'Bridge.openQuickSearch(' + LWasHiddenJs + ')');
|
||||
LogLine('Quick search requested from tray menu (wasHidden=' + LWasHiddenJs + ')');
|
||||
end;
|
||||
|
||||
procedure TMainForm.BridgeNewEntryHotkey(const AWindowTitle: string);
|
||||
@@ -529,6 +574,22 @@ begin
|
||||
LogLine('App brought to front (autofill picker)');
|
||||
end
|
||||
|
||||
// Used by the quick-search modal: after the user picks an entry the
|
||||
// password is on the clipboard — if the app was hidden when invoked
|
||||
// from the tray menu, hide it again so the user can paste straight
|
||||
// into the target app without alt-tabbing.
|
||||
else if ACmd = 'app/minimize' then
|
||||
FBridge.MinimizeToTray
|
||||
|
||||
// Tray balloon notifications on/off. JS pushes the user setting at
|
||||
// startup (settings_json sync) and whenever they flip the toggle.
|
||||
else if ACmd = 'tray/notifications' then
|
||||
begin
|
||||
FBridge.ShowNotifications := GetParam('enabled') = '1';
|
||||
LogLine('Tray notifications ' +
|
||||
IfThen(FBridge.ShowNotifications, 'enabled', 'disabled'));
|
||||
end
|
||||
|
||||
else if ACmd = 'app/ready' then
|
||||
begin
|
||||
WebBrowser.SetFocus;
|
||||
@@ -735,4 +796,32 @@ begin
|
||||
LogLine('Autofill hotkey (' + LKind + ') — foreground: "' + ATitle + '"');
|
||||
end;
|
||||
|
||||
initialization
|
||||
// WebView2 ships with a long list of "phone home" behaviours enabled by
|
||||
// default (SmartScreen lookups, component updates, sync, UMA telemetry,
|
||||
// domain reliability beacons, optimisation hints, etc.). For a vault
|
||||
// that's meant to be 100% offline we disable them by passing flags
|
||||
// through WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS — the standard way to
|
||||
// configure the embedded Chromium without touching system policy.
|
||||
//
|
||||
// Must be set BEFORE the TMS FNC WebBrowser instantiates its
|
||||
// CoreWebView2Environment, hence the unit initialization block.
|
||||
SetEnvironmentVariable('WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS',
|
||||
'--disable-background-networking ' +
|
||||
'--disable-sync ' +
|
||||
'--disable-component-update ' +
|
||||
'--no-default-browser-check ' +
|
||||
'--no-pings ' +
|
||||
'--disable-client-side-phishing-detection ' +
|
||||
'--disable-domain-reliability ' +
|
||||
'--disable-breakpad ' +
|
||||
'--disable-crash-reporter ' +
|
||||
'--no-experiments ' +
|
||||
'--metrics-recording-only ' +
|
||||
'--disable-features=MediaRouter,OptimizationHints,InterestFeedContentSuggestions,' +
|
||||
'CalculateNativeWinOcclusion,HardwareMediaKeyHandling,Translate,' +
|
||||
'NetworkServiceInProcess,BackgroundFetch,SafeBrowsingEnhancedProtection,' +
|
||||
'AutofillServerCommunication,PrivacySandboxAdsAPIsOverride'
|
||||
);
|
||||
|
||||
end.
|
||||
|
||||
Reference in New Issue
Block a user