Skip to content

Commit e231df1

Browse files
authored
WebView window event handling fixes (#6038)
* WebView fixes * UA override logic * fix * debug logs * alter all webviews * cookies stuff * remove debug stuff
1 parent 3052a14 commit e231df1

1 file changed

Lines changed: 253 additions & 19 deletions

File tree

apps/app/src/api/ads.rs

Lines changed: 253 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
use std::collections::HashSet;
2+
use std::sync::Arc;
3+
use std::sync::atomic::{AtomicBool, Ordering};
24
use std::time::{Duration, Instant};
35
use tauri::plugin::TauriPlugin;
46
use tauri::{Manager, PhysicalPosition, PhysicalSize, Runtime};
@@ -14,6 +16,148 @@ pub struct AdsState {
1416
}
1517

1618
const AD_LINK: &str = "https://modrinth.com/wrapper/app-ads-cookie";
19+
#[cfg(not(target_os = "linux"))]
20+
const ADS_USER_AGENT: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36";
21+
22+
#[cfg(windows)]
23+
fn ads_user_agent_override_params() -> String {
24+
serde_json::json!({
25+
"userAgent": ADS_USER_AGENT,
26+
"platform": "Win32",
27+
"userAgentMetadata": {
28+
"brands": [
29+
{ "brand": "Chromium", "version": "128" },
30+
{ "brand": "Google Chrome", "version": "128" },
31+
{ "brand": "Not=A?Brand", "version": "99" },
32+
],
33+
"fullVersion": "128.0.0.0",
34+
"fullVersionList": [
35+
{ "brand": "Chromium", "version": "128.0.0.0" },
36+
{ "brand": "Google Chrome", "version": "128.0.0.0" },
37+
{ "brand": "Not=A?Brand", "version": "99.0.0.0" },
38+
],
39+
"platform": "Windows",
40+
"platformVersion": "10.0.0",
41+
"architecture": "x86",
42+
"bitness": "64",
43+
"model": "",
44+
"mobile": false,
45+
},
46+
})
47+
.to_string()
48+
}
49+
50+
#[cfg(windows)]
51+
fn configure_ads_cookie_settings(
52+
core_webview2: &webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2,
53+
) {
54+
use webview2_com::Microsoft::Web::WebView2::Win32::{
55+
COREWEBVIEW2_TRACKING_PREVENTION_LEVEL_NONE, ICoreWebView2,
56+
ICoreWebView2_13, ICoreWebView2Profile3,
57+
};
58+
use windows_core::Interface;
59+
60+
match core_webview2
61+
.cast::<ICoreWebView2_13>()
62+
.and_then(|core_webview2| unsafe { core_webview2.Profile() })
63+
.and_then(|profile| profile.cast::<ICoreWebView2Profile3>())
64+
{
65+
Ok(profile) => {
66+
if let Err(error) = unsafe {
67+
profile.SetPreferredTrackingPreventionLevel(
68+
COREWEBVIEW2_TRACKING_PREVENTION_LEVEL_NONE,
69+
)
70+
} {
71+
tracing::warn!(
72+
?error,
73+
"Failed to disable ads WebView2 tracking prevention"
74+
);
75+
}
76+
}
77+
Err(error) => {
78+
tracing::warn!(
79+
?error,
80+
"Failed to access ads WebView2 profile tracking prevention settings"
81+
);
82+
}
83+
}
84+
}
85+
86+
fn set_webview_visible<R: Runtime>(
87+
webview: &tauri::Webview<R>,
88+
_visible: bool,
89+
) {
90+
webview
91+
.with_webview(
92+
#[allow(unused_variables)]
93+
move |wv| {
94+
#[cfg(windows)]
95+
{
96+
let controller = wv.controller();
97+
unsafe { controller.SetIsVisible(_visible) }.ok();
98+
}
99+
},
100+
)
101+
.ok();
102+
}
103+
104+
fn set_webview_visible_for_window<R: Runtime>(
105+
app: &tauri::AppHandle<R>,
106+
webview: &tauri::Webview<R>,
107+
visible: bool,
108+
) {
109+
let is_minimized = app
110+
.get_window("main")
111+
.and_then(|window| window.is_minimized().ok())
112+
.unwrap_or(false);
113+
114+
set_webview_visible(webview, visible && !is_minimized);
115+
}
116+
117+
fn sync_webview_visibility_for_main_window<R: Runtime>(
118+
app: &tauri::AppHandle<R>,
119+
main_window: &tauri::Window<R>,
120+
was_minimized: &AtomicBool,
121+
) {
122+
let is_minimized = main_window.is_minimized().unwrap_or(false);
123+
let was = was_minimized.load(Ordering::SeqCst);
124+
125+
if is_minimized == was {
126+
return;
127+
}
128+
129+
was_minimized.store(is_minimized, Ordering::SeqCst);
130+
131+
let ads_visible = if is_minimized {
132+
false
133+
} else {
134+
match app.state::<RwLock<AdsState>>().try_read() {
135+
Ok(state) => state.shown && !state.modal_shown,
136+
Err(_) => false,
137+
}
138+
};
139+
140+
let mut webviews = Vec::new();
141+
let mut seen_webviews = HashSet::new();
142+
143+
for webview in main_window.webviews() {
144+
seen_webviews.insert(webview.label().to_string());
145+
webviews.push(webview);
146+
}
147+
148+
for webview in app.webviews().into_values() {
149+
if seen_webviews.insert(webview.label().to_string()) {
150+
webviews.push(webview);
151+
}
152+
}
153+
154+
for webview in webviews {
155+
let visible =
156+
!is_minimized && (webview.label() != "ads-window" || ads_visible);
157+
158+
set_webview_visible(&webview, visible);
159+
}
160+
}
17161

18162
pub fn init<R: Runtime>() -> TauriPlugin<R> {
19163
tauri::plugin::Builder::<R>::new("ads")
@@ -30,10 +174,11 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
30174
// visible when we refresh, the Aditude wrapper will not make any ad requests
31175
// unless Chromium reports the page as visible. The refresh does not reset the
32176
// visibility state.
33-
let app = app.clone();
177+
let refresh_app = app.clone();
34178
tauri::async_runtime::spawn(async move {
35179
loop {
36-
if let Some(webview) = app.webviews().get_mut("ads-window")
180+
if let Some(webview) =
181+
refresh_app.webviews().get_mut("ads-window")
37182
{
38183
let _ = webview.navigate(AD_LINK.parse().unwrap());
39184
}
@@ -43,6 +188,34 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
43188
}
44189
});
45190

191+
if let Some(main_window) = app.get_window("main") {
192+
let app_handle = app.clone();
193+
let event_window = main_window.clone();
194+
let was_minimized = Arc::new(AtomicBool::new(false));
195+
196+
main_window.on_window_event(move |_| {
197+
sync_webview_visibility_for_main_window(
198+
&app_handle,
199+
&event_window,
200+
&was_minimized,
201+
);
202+
203+
let delayed_app_handle = app_handle.clone();
204+
let delayed_event_window = event_window.clone();
205+
let delayed_was_minimized = was_minimized.clone();
206+
207+
tauri::async_runtime::spawn(async move {
208+
tokio::time::sleep(Duration::from_millis(100)).await;
209+
210+
sync_webview_visibility_for_main_window(
211+
&delayed_app_handle,
212+
&delayed_event_window,
213+
&delayed_was_minimized,
214+
);
215+
});
216+
});
217+
}
218+
46219
Ok(())
47220
})
48221
.invoke_handler(tauri::generate_handler![
@@ -103,31 +276,38 @@ pub async fn init_ads_window<R: Runtime>(
103276
webview.show().ok();
104277
webview.set_position(position).ok();
105278
webview.set_size(size).ok();
279+
set_webview_visible_for_window(&app, webview, true);
106280
} else {
107281
webview.hide().ok();
108282
webview
109283
.set_position(PhysicalPosition::new(-1000, -1000))
110284
.ok();
285+
set_webview_visible(webview, false);
111286
}
112287

113288
Some(webview.clone())
114289
} else if let Some(window) = app.get_window("main") {
290+
#[cfg(windows)]
291+
let webview_url =
292+
WebviewUrl::External("about:blank".parse().unwrap());
293+
#[cfg(not(windows))]
294+
let webview_url = WebviewUrl::External(AD_LINK.parse().unwrap());
295+
115296
let webview = window.add_child(
116-
tauri::webview::WebviewBuilder::new(
117-
"ads-window",
118-
WebviewUrl::External(
119-
AD_LINK.parse().unwrap(),
120-
),
121-
)
122-
.initialization_script_for_all_frames(include_str!("ads-init.js"))
123-
// We use a standard Chrome user agent for compatibility with our ad provider,
124-
// since Tauri is not recognized by ad providers by default.
125-
// Aditude has separately informed SSPs and IVT vendors that this traffic
126-
// originates from a desktop app.
127-
.user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36")
128-
.zoom_hotkeys_enabled(false)
129-
.transparent(true)
130-
.on_new_window(|_, _| tauri::webview::NewWindowResponse::Deny),
297+
tauri::webview::WebviewBuilder::new("ads-window", webview_url)
298+
.initialization_script_for_all_frames(include_str!(
299+
"ads-init.js"
300+
))
301+
// We use a standard Chrome user agent for compatibility with our ad provider,
302+
// since Tauri is not recognized by ad providers by default.
303+
// Aditude has separately informed SSPs and IVT vendors that this traffic
304+
// originates from a desktop app.
305+
.user_agent(ADS_USER_AGENT)
306+
.zoom_hotkeys_enabled(false)
307+
.transparent(true)
308+
.on_new_window(|_, _| {
309+
tauri::webview::NewWindowResponse::Deny
310+
}),
131311
// set both the `hide`/`show` state and `position`,
132312
// to ensure that the webview is actually shown/hidden
133313
if state.shown {
@@ -140,15 +320,68 @@ pub async fn init_ads_window<R: Runtime>(
140320

141321
if state.shown {
142322
webview.show().ok();
323+
set_webview_visible_for_window(&app, &webview, true);
143324
} else {
144325
webview.hide().ok();
326+
set_webview_visible(&webview, false);
145327
}
146328

147329
webview.with_webview(#[allow(unused_variables)] |webview2| {
148330
#[cfg(windows)]
149331
{
332+
use webview2_com::CallDevToolsProtocolMethodCompletedHandler;
150333
use webview2_com::Microsoft::Web::WebView2::Win32::ICoreWebView2_8;
151334
use windows_core::Interface;
335+
use windows_core::HSTRING;
336+
337+
let core_webview2 =
338+
unsafe { webview2.controller().CoreWebView2() };
339+
340+
if let Ok(core_webview2) = core_webview2 {
341+
configure_ads_cookie_settings(&core_webview2);
342+
343+
let navigate_webview = core_webview2.clone();
344+
let handler =
345+
CallDevToolsProtocolMethodCompletedHandler::create(
346+
Box::new(move |result: windows_core::Result<()>, _| {
347+
if let Err(error) = result {
348+
tracing::error!(
349+
?error,
350+
"Failed to override ads user-agent client hints"
351+
);
352+
}
353+
354+
unsafe {
355+
navigate_webview
356+
.Navigate(&HSTRING::from(AD_LINK))
357+
.ok();
358+
}
359+
360+
Ok(())
361+
}) as Box<_>,
362+
);
363+
364+
unsafe {
365+
if let Err(error) = core_webview2
366+
.CallDevToolsProtocolMethod(
367+
&HSTRING::from(
368+
"Emulation.setUserAgentOverride",
369+
),
370+
&HSTRING::from(
371+
ads_user_agent_override_params(),
372+
),
373+
&handler,
374+
)
375+
{
376+
tracing::error!(
377+
?error,
378+
"Failed to install ads user-agent client hints override"
379+
);
380+
381+
core_webview2.Navigate(&HSTRING::from(AD_LINK)).ok();
382+
}
383+
}
384+
}
152385

153386
let webview2_controller = webview2.controller();
154387
let Ok(webview2_8) = unsafe { webview2_controller.CoreWebView2() }
@@ -166,9 +399,9 @@ pub async fn init_ads_window<R: Runtime>(
166399
None
167400
};
168401

169-
let Some(webview) = webview.clone() else {
402+
if webview.is_none() {
170403
return Ok(());
171-
};
404+
}
172405

173406
// tauri::async_runtime::spawn(async move {
174407
// loop {
@@ -249,6 +482,7 @@ pub async fn show_ads_window<R: Runtime>(
249482
webview.set_size(size).ok();
250483
webview.set_position(position).ok();
251484
webview.show().ok();
485+
set_webview_visible_for_window(&app, webview, true);
252486
}
253487
}
254488

0 commit comments

Comments
 (0)