11use std:: collections:: HashSet ;
2+ use std:: sync:: Arc ;
3+ use std:: sync:: atomic:: { AtomicBool , Ordering } ;
24use std:: time:: { Duration , Instant } ;
35use tauri:: plugin:: TauriPlugin ;
46use tauri:: { Manager , PhysicalPosition , PhysicalSize , Runtime } ;
@@ -14,6 +16,148 @@ pub struct AdsState {
1416}
1517
1618const 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
18162pub 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