mirror of
https://github.com/modrinth/code.git
synced 2026-08-25 09:04:55 +00:00
fix: privacy popup in app bounded to tiny webview (#6657)
This commit is contained in:
+157
-18
@@ -11,12 +11,14 @@ use tokio::sync::RwLock;
|
||||
pub struct AdsState {
|
||||
pub shown: bool,
|
||||
pub modal_shown: bool,
|
||||
pub consent_overlay_shown: bool,
|
||||
pub occluded: bool,
|
||||
pub last_click: Option<Instant>,
|
||||
pub malicious_origins: HashSet<String>,
|
||||
}
|
||||
|
||||
const AD_LINK: &str = "https://modrinth.com/wrapper/app-ads-cookie";
|
||||
const APP_TITLE_BAR_HEIGHT: f32 = 48.0;
|
||||
#[cfg(any(windows, target_os = "macos"))]
|
||||
pub(super) const OCCLUDED_AREA_THRESHOLD: f64 = 0.5;
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
@@ -131,13 +133,16 @@ fn set_webview_visible_for_window<R: Runtime>(
|
||||
.and_then(|window| window.is_minimized().ok())
|
||||
.unwrap_or(false);
|
||||
|
||||
let is_occluded = app
|
||||
let (is_occluded, consent_overlay_shown) = app
|
||||
.state::<RwLock<AdsState>>()
|
||||
.try_read()
|
||||
.map(|state| state.occluded)
|
||||
.unwrap_or(false);
|
||||
.map(|state| (state.occluded, state.consent_overlay_shown))
|
||||
.unwrap_or((false, false));
|
||||
|
||||
set_webview_visible(webview, visible && !is_minimized && !is_occluded);
|
||||
set_webview_visible(
|
||||
webview,
|
||||
visible && !is_minimized && (!is_occluded || consent_overlay_shown),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(any(windows, target_os = "macos"))]
|
||||
@@ -195,7 +200,8 @@ async fn sync_ads_occlusion<R: Runtime>(app: &tauri::AppHandle<R>) {
|
||||
}
|
||||
|
||||
state.occluded = occluded;
|
||||
let visible = state.shown && !state.modal_shown;
|
||||
let visible =
|
||||
state.shown && (!state.modal_shown || state.consent_overlay_shown);
|
||||
drop(state);
|
||||
|
||||
if let Some(webview) = app.webviews().get("ads-window") {
|
||||
@@ -211,21 +217,36 @@ fn sync_webview_visibility_for_main_window<R: Runtime>(
|
||||
let is_minimized = main_window.is_minimized().unwrap_or(false);
|
||||
let was = was_minimized.load(Ordering::SeqCst);
|
||||
|
||||
let ads_state = if is_minimized {
|
||||
None
|
||||
} else {
|
||||
match app.state::<RwLock<AdsState>>().try_read() {
|
||||
Ok(state) => Some((
|
||||
state.shown
|
||||
&& (!state.modal_shown || state.consent_overlay_shown)
|
||||
&& (!state.occluded || state.consent_overlay_shown),
|
||||
state.consent_overlay_shown,
|
||||
)),
|
||||
Err(_) => None,
|
||||
}
|
||||
};
|
||||
let ads_visible = ads_state.map(|state| state.0).unwrap_or(false);
|
||||
|
||||
if ads_state.map(|state| state.1).unwrap_or(false)
|
||||
&& let Some(webview) = app.webviews().get("ads-window")
|
||||
&& let Ok((position, size)) =
|
||||
get_overlay_webview_position_for_window(main_window)
|
||||
{
|
||||
webview.set_position(position).ok();
|
||||
webview.set_size(size).ok();
|
||||
}
|
||||
|
||||
if is_minimized == was {
|
||||
return;
|
||||
}
|
||||
|
||||
was_minimized.store(is_minimized, Ordering::SeqCst);
|
||||
|
||||
let ads_visible = if is_minimized {
|
||||
false
|
||||
} else {
|
||||
match app.state::<RwLock<AdsState>>().try_read() {
|
||||
Ok(state) => state.shown && !state.modal_shown && !state.occluded,
|
||||
Err(_) => false,
|
||||
}
|
||||
};
|
||||
|
||||
let mut webviews = Vec::new();
|
||||
let mut seen_webviews = HashSet::new();
|
||||
|
||||
@@ -254,6 +275,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
app.manage(RwLock::new(AdsState {
|
||||
shown: true,
|
||||
modal_shown: false,
|
||||
consent_overlay_shown: false,
|
||||
occluded: false,
|
||||
last_click: None,
|
||||
malicious_origins: HashSet::new(),
|
||||
@@ -269,7 +291,10 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
.state::<RwLock<AdsState>>()
|
||||
.try_read()
|
||||
.map(|state| {
|
||||
state.shown && !state.modal_shown && !state.occluded
|
||||
state.shown
|
||||
&& !state.modal_shown
|
||||
&& !state.consent_overlay_shown
|
||||
&& !state.occluded
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
@@ -332,6 +357,8 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
init_ads_window,
|
||||
hide_ads_window,
|
||||
show_ads_window,
|
||||
show_ads_consent_overlay,
|
||||
hide_ads_consent_overlay,
|
||||
record_ads_click,
|
||||
open_link,
|
||||
get_ads_personalization,
|
||||
@@ -358,6 +385,42 @@ fn get_webview_position<R: Runtime>(
|
||||
))
|
||||
}
|
||||
|
||||
fn get_overlay_webview_position_for_window<R: Runtime>(
|
||||
main_window: &tauri::Window<R>,
|
||||
) -> crate::api::Result<(PhysicalPosition<f32>, PhysicalSize<f32>)> {
|
||||
let main_window_size = main_window.outer_size()?;
|
||||
let title_bar_height =
|
||||
APP_TITLE_BAR_HEIGHT * main_window.scale_factor()? as f32;
|
||||
|
||||
Ok((
|
||||
PhysicalPosition::new(0.0, title_bar_height),
|
||||
PhysicalSize::new(
|
||||
main_window_size.width as f32,
|
||||
(main_window_size.height as f32 - title_bar_height).max(0.0),
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
fn get_overlay_webview_position<R: Runtime>(
|
||||
app: &tauri::AppHandle<R>,
|
||||
) -> crate::api::Result<(PhysicalPosition<f32>, PhysicalSize<f32>)> {
|
||||
let main_window = app.get_window("main").unwrap();
|
||||
|
||||
get_overlay_webview_position_for_window(&main_window)
|
||||
}
|
||||
|
||||
fn get_device_pixel_ratio<R: Runtime>(
|
||||
app: &tauri::AppHandle<R>,
|
||||
dpr: Option<f32>,
|
||||
) -> f32 {
|
||||
dpr.unwrap_or_else(|| {
|
||||
app.get_window("main")
|
||||
.and_then(|window| window.scale_factor().ok())
|
||||
.map(|scale_factor| scale_factor as f32)
|
||||
.unwrap_or(1.0)
|
||||
})
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
pub async fn init_ads_window<R: Runtime>(
|
||||
@@ -374,11 +437,17 @@ pub async fn init_ads_window<R: Runtime>(
|
||||
state.shown = true;
|
||||
}
|
||||
|
||||
if state.modal_shown {
|
||||
if state.modal_shown && !state.consent_overlay_shown {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Ok((position, size)) = get_webview_position(&app, dpr) {
|
||||
let layout = if state.consent_overlay_shown {
|
||||
get_overlay_webview_position(&app)
|
||||
} else {
|
||||
get_webview_position(&app, dpr)
|
||||
};
|
||||
|
||||
if let Ok((position, size)) = layout {
|
||||
let webview = if let Some(webview) = app.webviews().get("ads-window") {
|
||||
// set both the `hide`/`show` state and `position`,
|
||||
// to ensure that the webview is actually shown/hidden
|
||||
@@ -586,7 +655,11 @@ pub async fn show_ads_window<R: Runtime>(
|
||||
state.modal_shown = false;
|
||||
|
||||
if state.shown {
|
||||
let (position, size) = get_webview_position(&app, dpr)?;
|
||||
let (position, size) = if state.consent_overlay_shown {
|
||||
get_overlay_webview_position(&app)?
|
||||
} else {
|
||||
get_webview_position(&app, dpr)?
|
||||
};
|
||||
// set both the `hide`/`show` state and `position`,
|
||||
// to ensure that the webview is actually shown/hidden
|
||||
webview.set_size(size).ok();
|
||||
@@ -610,8 +683,19 @@ pub async fn hide_ads_window<R: Runtime>(
|
||||
|
||||
if reset.unwrap_or(false) {
|
||||
state.shown = false;
|
||||
state.consent_overlay_shown = false;
|
||||
} else {
|
||||
state.modal_shown = true;
|
||||
|
||||
if state.consent_overlay_shown {
|
||||
let (position, size) = get_overlay_webview_position(&app)?;
|
||||
webview.set_size(size).ok();
|
||||
webview.set_position(position).ok();
|
||||
webview.show().ok();
|
||||
set_webview_visible_for_window(&app, webview, true);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// set both the `hide`/`show` state and `position`,
|
||||
@@ -625,6 +709,61 @@ pub async fn hide_ads_window<R: Runtime>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn show_ads_consent_overlay<R: Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
) -> crate::api::Result<()> {
|
||||
if let Some(webview) = app.webviews().get("ads-window") {
|
||||
let state = app.state::<RwLock<AdsState>>();
|
||||
let mut state = state.write().await;
|
||||
|
||||
// dont show for hidden ads so consent events cannot re-enable the webview.
|
||||
if !state.shown {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
state.consent_overlay_shown = true;
|
||||
|
||||
let (position, size) = get_overlay_webview_position(&app)?;
|
||||
webview.set_size(size).ok();
|
||||
webview.set_position(position).ok();
|
||||
webview.show().ok();
|
||||
set_webview_visible_for_window(&app, webview, true);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn hide_ads_consent_overlay<R: Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
dpr: Option<f32>,
|
||||
) -> crate::api::Result<()> {
|
||||
if let Some(webview) = app.webviews().get("ads-window") {
|
||||
let state = app.state::<RwLock<AdsState>>();
|
||||
let mut state = state.write().await;
|
||||
|
||||
state.consent_overlay_shown = false;
|
||||
|
||||
if state.shown && !state.modal_shown {
|
||||
let dpr = get_device_pixel_ratio(&app, dpr);
|
||||
let (position, size) = get_webview_position(&app, dpr)?;
|
||||
|
||||
webview.set_size(size).ok();
|
||||
webview.set_position(position).ok();
|
||||
webview.show().ok();
|
||||
set_webview_visible_for_window(&app, webview, true);
|
||||
} else {
|
||||
webview
|
||||
.set_position(PhysicalPosition::new(-1000, -1000))
|
||||
.ok();
|
||||
webview.hide().ok();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn record_ads_click<R: Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
|
||||
Reference in New Issue
Block a user