1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-08 12:40:26 +00:00

Add native transfer_focus() method

This commit is contained in:
Isaiah Inuwa
2025-12-02 11:30:58 -06:00
parent 4e7622bcf1
commit 411b2bcf88
9 changed files with 65 additions and 9 deletions

View File

@@ -938,6 +938,7 @@ dependencies = [
"tokio",
"tracing",
"tracing-subscriber",
"windows 0.61.3",
"windows-registry",
"windows_plugin_authenticator",
]

View File

@@ -27,6 +27,7 @@ tracing = { workspace = true }
tracing-subscriber = { workspace = true }
[target.'cfg(windows)'.dependencies]
windows = { workspace = true }
windows-registry = { workspace = true }
windows_plugin_authenticator = { path = "../windows_plugin_authenticator" }

View File

@@ -147,6 +147,7 @@ export declare namespace autostart {
}
export declare namespace autofill {
export function runCommand(value: string): Promise<string>
export function transferFocus(handle: Array<number>): Promise<void>
export const enum UserVerification {
Preferred = 'preferred',
Required = 'required',

View File

@@ -636,6 +636,8 @@ pub mod autofill {
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use tracing::error;
use crate::passkey_authenticator_internal;
#[napi]
pub async fn run_command(value: String) -> napi::Result<String> {
desktop_core::autofill::run_command(value)
@@ -643,6 +645,12 @@ pub mod autofill {
.map_err(|e| napi::Error::from_reason(e.to_string()))
}
#[napi]
pub async fn transfer_focus(handle: Vec<u8>) -> napi::Result<()> {
passkey_authenticator_internal::transfer_focus(handle)
.map_err(|e| napi::Error::from_reason(e.to_string()))
}
#[derive(Debug, serde::Serialize, serde:: Deserialize)]
pub enum BitwardenError {
Internal(String),

View File

@@ -3,3 +3,7 @@ use anyhow::{bail, Result};
pub fn register() -> Result<()> {
bail!("Not implemented")
}
pub fn transfer_focus(handle: Vec<u8>) -> Result<()> {
bail!("Not implemented")
}

View File

@@ -1,7 +1,33 @@
use anyhow::{anyhow, Result};
use windows::Win32::{
Foundation::HWND,
UI::{Input::KeyboardAndMouse::SetFocus, WindowsAndMessaging::BringWindowToTop},
};
pub fn register() -> Result<()> {
windows_plugin_authenticator::register().map_err(|e| anyhow!(e))?;
Ok(())
}
pub fn transfer_focus(handle: Vec<u8>) -> Result<()> {
unsafe {
// SAFETY: We check to make sure that the vec is the expected size
// before converting it. If the handle is invalid when passed to
// Windows, the request will be rejected.
if handle.len() != size_of::<HWND>() {
return Err(anyhow!("Invalid window handle received: {:?}", handle));
}
let hwnd = *handle.as_ptr().cast();
tracing::debug!("Transferring focus to {hwnd:?}");
let result = SetFocus(Some(hwnd));
tracing::debug!("SetFocus? {result:?}");
let result = BringWindowToTop(hwnd);
tracing::debug!("BringWindowToTop? {result:?}");
}
Ok(())
}

View File

@@ -12,6 +12,8 @@ export default {
runCommand: <C extends Command>(params: RunCommandParams<C>): Promise<RunCommandResult<C>> =>
ipcRenderer.invoke("autofill.runCommand", params),
transferFocus: (handle: Uint8Array) => ipcRenderer.invoke("autofill.transferFocus", handle),
listenerReady: () => ipcRenderer.send("autofill.listenerReady"),
listenPasskeyRegistration: (

View File

@@ -383,9 +383,11 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi
// TODO: modalState is just a proxy for what we actually want: whether the window is visible.
// We should add a way for services to query window visibility.
const modalState = await firstValueFrom(this.desktopSettingsService.modalMode$);
const windowHandle = modalState.isModalModeActive ? await ipc.platform.getNativeWindowHandle() : this.windowObject.handle;
const uvRequest = ipc.autofill.runCommand<NativeAutofillUserVerificationCommand>({
await ipc.autofill.transferFocus(windowHandle);
// Ensure our window is hidden when showing the OS user verification dialog.
this.logService.debug("Hiding UI");
this.hideUi();
const uvResult = await ipc.autofill.runCommand<NativeAutofillUserVerificationCommand>({
namespace: "autofill",
command: "user-verification",
params: {
@@ -395,12 +397,6 @@ export class DesktopFido2UserInterfaceSession implements Fido2UserInterfaceSessi
displayHint,
},
});
// Ensure our window is hidden when showing the OS user verification dialog.
// TODO: This is prone to data races and, on Windows, may cause the Windows
// Hello dialog not to have keyboard input focus. We need a better solution
// than this.
this.hideUi();
const uvResult = await uvRequest;
if (uvResult.type === "error") {
this.logService.error("Error getting user verification", uvResult.error);
return false;

View File

@@ -84,6 +84,17 @@ export class NativeAutofillMain {
},
);
ipcMain.handle(
"autofill.transferFocus",
(
_event: any,
handle: Uint8Array,
): Promise<void> => {
return this.transferFocus(handle);
},
);
this.ipcServer = await autofill.IpcServer.listen(
"af",
// RegistrationCallback
@@ -228,4 +239,10 @@ export class NativeAutofillMain {
return { type: "error", error: String(e) } as RunCommandResult<C>;
}
}
private transferFocus(handle: Uint8Array): Promise<void> {
const h = Array.from(handle);
this.logService.debug("Transferring focus to", h);
return autofill.transferFocus(h);
}
}