Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changes/unstable-webview-focus.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
tauri: minor:bug
tauri-runtime-wry: minor:bug
---

Fix webview don't get focus when Alt-Tab back to the window if `unstable` feature is enabled on Windows
6 changes: 6 additions & 0 deletions .changes/webview-focus-on-move.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
tauri: minor:bug
tauri-runtime-wry: minor:bug
---

Fix `WindowEvent::Focused` events emitted when dragging the window on Windows
5 changes: 5 additions & 0 deletions .changes/wry-focused-webview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
tauri-runtime-wry: minor:breaking
---

`CreateWebviewOptions::focused_webview` now takes `Arc<Mutex<FocusState>>` instead of `Arc<Mutex<Option<String>>>`
232 changes: 152 additions & 80 deletions crates/tauri-runtime-wry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ use tao::platform::unix::{WindowBuilderExtUnix, WindowExtUnix};
#[cfg(windows)]
use tao::platform::windows::{WindowBuilderExtWindows, WindowExtWindows};
#[cfg(windows)]
use webview2_com::{ContainsFullScreenElementChangedEventHandler, FocusChangedEventHandler};
use webview2_com::{
ContainsFullScreenElementChangedEventHandler, FocusChangedEventHandler,
Microsoft::Web::WebView2::Win32::ICoreWebView2Controller,
};
#[cfg(windows)]
use windows::Win32::Foundation::HWND;
#[cfg(target_os = "ios")]
Expand Down Expand Up @@ -514,31 +517,42 @@ impl WindowEventWrapper {
// (without receiving a webview focus, such as when clicking the taskbar app icon or using Alt + Tab)
// in this case we must send the focus change event here
#[cfg(windows)]
#[allow(clippy::collapsible_match)]
if window.has_children.load(Ordering::Relaxed) {
const FOCUSED_WEBVIEW_MARKER: &str = "__tauriWindow?";
let mut focused_webview = window.focused_webview.lock().unwrap();
// when we focus a webview and the window was previously focused, we get a blur event here
// so on blur we should only send events if the current focus is owned by the window
if !*focused
&& focused_webview
.as_deref()
.is_some_and(|w| w != FOCUSED_WEBVIEW_MARKER)
{
if !*focused {
// Blur events are handled in the webview side (add_LostFocus)
return Self(None);
}

// reset focused_webview on blur, or set to a dummy value on focus
// (to prevent double focus event when we click a webview after focusing a window)
*focused_webview = if *focused {
Some(FOCUSED_WEBVIEW_MARKER.to_owned())
let mut focused_webview = window.focused_webview.lock().unwrap();
if let FocusState::Blured {
last_focused_webview_label,
} = &*focused_webview
{
let should_focus_webview =
last_focused_webview_label
.as_deref()
.and_then(|last_focused_webview_label| {
window
.webviews
.iter()
.find(|w| w.label == last_focused_webview_label)
});
*focused_webview = FocusState::WindowFocused;
if let Some(should_focus_webview) = should_focus_webview {
drop(focused_webview);
let _ = should_focus_webview.focus();
}
WindowEvent::Focused(true)
} else {
None
};

// Already focused
return Self(None);
}
} else if window.webviews.is_empty() {
// Raw tao window without webviews, forward the event
WindowEvent::Focused(*focused)
} else {
// when not on multiwebview mode, we handle focus change events on the webview (add_GotFocus and add_LostFocus)
// when not on multiwebview mode, wry will set focus to the webview,
// and we will handle focus change events on the webview (add_GotFocus and add_LostFocus)
return Self(None);
}
}
Expand Down Expand Up @@ -1469,7 +1483,7 @@ pub type CreateWebviewClosure =
Box<dyn FnOnce(&Window, CreateWebviewOptions) -> Result<WebviewWrapper> + Send>;

pub struct CreateWebviewOptions {
pub focused_webview: Arc<Mutex<Option<String>>>,
pub focused_webview: Arc<Mutex<FocusState>>,
}

pub enum Message<T: 'static> {
Expand Down Expand Up @@ -2505,6 +2519,25 @@ impl Drop for WebviewWrapper {
}
}

#[derive(Debug)]
pub enum FocusState {
WindowFocused,
WebviewFocused {
webview_label: String,
},
Blured {
last_focused_webview_label: Option<String>,
},
}

impl Default for FocusState {
fn default() -> Self {
Self::Blured {
last_focused_webview_label: None,
}
}
}

pub struct WindowWrapper {
label: String,
inner: Option<Arc<Window>>,
Expand All @@ -2519,7 +2552,7 @@ pub struct WindowWrapper {
is_window_transparent: bool,
#[cfg(windows)]
surface: Option<softbuffer::Surface<Arc<Window>, Arc<Window>>>,
focused_webview: Arc<Mutex<Option<String>>>,
focused_webview: Arc<Mutex<FocusState>>,
}

impl WindowWrapper {
Expand Down Expand Up @@ -4580,7 +4613,12 @@ fn create_window<T: UserEvent, F: Fn(RawWindow) + Send + 'static>(

let mut webviews = Vec::new();

let focused_webview = Arc::new(Mutex::new(None));
let focused_webview = Arc::new(Mutex::new(FocusState::default()));

#[cfg(feature = "unstable")]
let has_children = webview.is_some();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lucasfernog You might want to double check if this is correct

#[cfg(not(feature = "unstable"))]
let has_children = false;

if let Some(webview) = webview {
webviews.push(create_webview(
Expand Down Expand Up @@ -4617,7 +4655,7 @@ fn create_window<T: UserEvent, F: Fn(RawWindow) + Send + 'static>(

Ok(WindowWrapper {
label,
has_children: AtomicBool::new(false),
has_children: AtomicBool::new(has_children),
inner: Some(window),
webviews,
window_event_listeners,
Expand Down Expand Up @@ -4655,7 +4693,7 @@ fn create_webview<T: UserEvent>(
id: WebviewId,
context: &Context<T>,
pending: PendingWebview<T, Wry<T>>,
#[allow(unused_variables)] focused_webview: Arc<Mutex<Option<String>>>,
#[allow(unused_variables)] focused_webview: Arc<Mutex<FocusState>>,
) -> Result<WebviewWrapper> {
if !context.webview_runtime_installed {
#[cfg(all(not(debug_assertions), windows))]
Expand Down Expand Up @@ -5191,64 +5229,17 @@ You may have it installed on another user account, but it is not available for t
#[cfg(windows)]
{
let controller = webview.controller();
let proxy_clone = context.proxy.clone();
let window_id_ = window_id.clone();
let mut token = 0;
unsafe {
let label_ = label.clone();
let focused_webview_ = focused_webview.clone();
controller.add_GotFocus(
&FocusChangedEventHandler::create(Box::new(move |_, _| {
let mut focused_webview = focused_webview_.lock().unwrap();
// when using multiwebview mode, we should check if the focus change is actually a "webview focus change"
// instead of a window focus change (here we're patching window events, so we only care about the actual window changing focus)
let already_focused = focused_webview.is_some();
focused_webview.replace(label_.clone());

if !already_focused {
let _ = proxy_clone.send_event(Message::Webview(
*window_id_.lock().unwrap(),
id,
WebviewMessage::SynthesizedWindowEvent(SynthesizedWindowEvent::Focused(true)),
));
}
Ok(())
})),
&mut token,
)
}
.unwrap();
unsafe {
let label_ = label.clone();
let window_id_ = window_id.clone();
let proxy_clone = context.proxy.clone();
controller.add_LostFocus(
&FocusChangedEventHandler::create(Box::new(move |_, _| {
let mut focused_webview = focused_webview.lock().unwrap();
// when using multiwebview mode, we should handle webview focus changes
// so we check is the currently focused webview matches this webview's
// (in this case, it means we lost the window focus)
//
// on multiwebview mode if we change focus to a different webview
// we get the gotFocus event of the other webview before the lostFocus
// so this check makes sense
let lost_window_focus = focused_webview.as_ref().is_none_or(|t| t == &label_);

if lost_window_focus {
// only reset when we lost window focus - otherwise some other webview is focused
*focused_webview = None;
let _ = proxy_clone.send_event(Message::Webview(
*window_id_.lock().unwrap(),
id,
WebviewMessage::SynthesizedWindowEvent(SynthesizedWindowEvent::Focused(false)),
));
}
Ok(())
})),
&mut token,
)
}
.unwrap();
add_focus_change_listeners(
window_id.clone(),
id,
context.proxy.clone(),
focused_webview,
label.clone(),
&controller,
&mut token,
);

if let Ok(webview) = unsafe { controller.CoreWebView2() } {
let proxy_clone = context.proxy.clone();
Expand Down Expand Up @@ -5346,3 +5337,84 @@ fn to_tao_theme(theme: Option<Theme>) -> Option<TaoTheme> {
_ => None,
}
}

/// Used to prevent duplicated [`WindowEvent::Focused`] events,
/// and to track last focused webview in multi-webview mode for us to restore webview focuses
#[cfg(windows)]
fn add_focus_change_listeners<T: UserEvent>(
window_id: Arc<Mutex<WindowId>>,
id: u32,
proxy: TaoEventLoopProxy<Message<T>>,
focused_webview: Arc<Mutex<FocusState>>,
label: String,
controller: &ICoreWebView2Controller,
token: &mut i64,
) {
let label_ = label.clone();
let window_id_ = window_id.clone();
let proxy_clone = proxy.clone();
let focused_webview_ = focused_webview.clone();
if let Err(error) = unsafe {
controller.add_GotFocus(
&FocusChangedEventHandler::create(Box::new(move |_, _| {
let mut focused_webview = focused_webview_.lock().unwrap();
// when using multiwebview mode, we should check if the focus change is actually a "webview focus change"
// instead of a window focus change (here we're patching window events, so we only care about the actual window changing focus)
let already_focused = matches!(
*focused_webview,
FocusState::WindowFocused | FocusState::WebviewFocused { .. }
);
*focused_webview = FocusState::WebviewFocused {
webview_label: label_.clone(),
};

if !already_focused {
let _ = proxy_clone.send_event(Message::Webview(
*window_id_.lock().unwrap(),
id,
WebviewMessage::SynthesizedWindowEvent(SynthesizedWindowEvent::Focused(true)),
));
}
Ok(())
})),
token,
)
} {
log::error!("Failed to attach WebView2 `add_GotFocus` handler, `WindowEvent::Focused` will not be sent: {error}");
return;
}

if let Err(error) = unsafe {
controller.add_LostFocus(
&FocusChangedEventHandler::create(Box::new(move |_, _| {
let mut focused_webview = focused_webview.lock().unwrap();
// when using multiwebview mode, we should handle webview focus changes
// so we check is the currently focused webview matches this webview's
// (in this case, it means we lost the window focus)
//
// on multiwebview mode if we change focus to a different webview
// we get the gotFocus event of the other webview before the lostFocus
// so this check makes sense
if let FocusState::WebviewFocused { ref webview_label } = *focused_webview {
let lost_window_focus = webview_label == &label;
if lost_window_focus {
// only reset when we lost window focus - otherwise some other webview is focused
*focused_webview = FocusState::Blured {
last_focused_webview_label: Some(label.clone()),
};
let _ = proxy.send_event(Message::Webview(
*window_id.lock().unwrap(),
id,
WebviewMessage::SynthesizedWindowEvent(SynthesizedWindowEvent::Focused(false)),
));
}
}

Ok(())
})),
token,
)
} {
log::error!("Failed to attach WebView2 `add_LostFocus` handler, `WindowEvent::Focused` will not be sent: {error}");
}
}
Loading