From 7397d5c11440f00276bfe620db70709ddc078553 Mon Sep 17 00:00:00 2001 From: Mega Mind <146339422+M3gA-Mind@users.noreply.github.com> Date: Fri, 24 Apr 2026 22:23:36 +0530 Subject: [PATCH] fix(webview): avoid data url placeholder for child accounts (#884) (#891) --- app/src-tauri/src/cdp/mod.rs | 2 +- app/src-tauri/src/cdp/session.rs | 93 ++++++++++++++++------- app/src-tauri/src/webview_accounts/mod.rs | 16 ++-- 3 files changed, 75 insertions(+), 36 deletions(-) diff --git a/app/src-tauri/src/cdp/mod.rs b/app/src-tauri/src/cdp/mod.rs index 94efe1123..0272337cb 100644 --- a/app/src-tauri/src/cdp/mod.rs +++ b/app/src-tauri/src/cdp/mod.rs @@ -16,7 +16,7 @@ pub mod target; pub use conn::CdpConn; pub use emulation::{set_user_agent_override, UaSpec}; -pub use session::{placeholder_data_url, placeholder_marker, spawn_session, target_url_fragment}; +pub use session::{placeholder_marker, placeholder_url, spawn_session, target_url_fragment}; pub use snapshot::Snapshot; pub use target::{ browser_ws_url, connect_and_attach_matching, detach_session, find_page_target_where, diff --git a/app/src-tauri/src/cdp/session.rs b/app/src-tauri/src/cdp/session.rs index a4c7b5265..e47b16831 100644 --- a/app/src-tauri/src/cdp/session.rs +++ b/app/src-tauri/src/cdp/session.rs @@ -7,12 +7,12 @@ //! session detaches. If we attached just once and dropped, subsequent HTTP //! requests + navigator reads would revert to WKWebView defaults. //! -//! Pairs with the `data:` placeholder URL the webview is created with — -//! the opener finds the target by its unique `openhuman:{account_id}` -//! marker in the initial URL, applies the UA override, then navigates the -//! target to the real provider URL with a `#openhuman-account-{id}` -//! fragment appended so other scanners (discord/telegram/slack/whatsapp) -//! can disambiguate multi-account setups without title-marker injection. +//! Pairs with the placeholder URL the webview is created with — the opener +//! finds the target by its unique `openhuman:{account_id}` marker in the +//! initial URL, applies the UA override, then navigates the target to the +//! real provider URL with a `#openhuman-account-{id}` fragment appended so +//! other scanners (discord/telegram/slack/whatsapp) can disambiguate +//! multi-account setups without title-marker injection. use std::time::Duration; @@ -28,9 +28,7 @@ use super::{browser_ws_url, find_page_target_where, set_user_agent_override, Cdp const ATTACH_BACKOFF: Duration = Duration::from_secs(2); /// Returns the unique marker substring that the account's initial -/// placeholder URL contains so `Target.getTargets` can identify it. Same -/// marker is embedded into the document title of the placeholder so -/// `TargetInfo.title` can also be used as a fallback match key. +/// placeholder URL contains so `Target.getTargets` can identify it. pub fn placeholder_marker(account_id: &str) -> String { format!("openhuman-acct-{account_id}") } @@ -41,15 +39,21 @@ pub fn target_url_fragment(account_id: &str) -> String { format!("#openhuman-account-{account_id}") } -/// Build the `data:` URL used as the webview's initial location. Holding -/// here for the ~hundreds of ms we need to attach CDP + apply overrides -/// before the first real HTTP request. URL-encoded by hand (the payload -/// is tiny, no external dep). -pub fn placeholder_data_url(account_id: &str) -> String { +/// Build the placeholder URL used as the webview's initial location. +/// `about:blank` is sufficient for the short holding page we need while CDP +/// attaches and applies overrides before the first real HTTP request. +/// +/// We store the account marker in the fragment so `TargetInfo.url` stays +/// unique per account without depending on Tauri's optional `data:` support. +pub fn placeholder_url(account_id: &str) -> String { + format!("about:blank#{}", placeholder_marker(account_id)) +} + +fn target_matches_account_url(target_url: &str, account_id: &str) -> bool { let marker = placeholder_marker(account_id); - format!( - "data:text/html;charset=utf-8,%3C%21DOCTYPE%20html%3E%3Ctitle%3E{marker}%3C%2Ftitle%3E%3Cbody%20style%3D%22background%3A%23111%22%3E%3C%2Fbody%3E" - ) + let marker_fragment = format!("#{marker}"); + let fragment = target_url_fragment(account_id); + target_url.ends_with(&marker_fragment) || target_url.ends_with(&fragment) } /// Spawn the per-account CDP session. Returns immediately; the background @@ -74,7 +78,7 @@ async fn run_session_forever(account_id: String, real_url: String) { placeholder_marker(&account_id) ); // Let the webview's target appear in CDP before we start hammering - // `/json/version`. The placeholder URL is tiny so this is quick. + // `/json/version`. The placeholder URL is trivial so this is quick. sleep(Duration::from_millis(500)).await; loop { match run_session_cycle(&account_id, &real_url).await { @@ -96,16 +100,13 @@ async fn run_session_cycle(account_id: &str, real_url: &str) -> Result<(), Strin let browser_ws = browser_ws_url().await?; let mut cdp = CdpConn::open(&browser_ws).await?; - // Account-unique match. Both the placeholder title and the real-URL - // fragment are appended verbatim, so we can use ends_with / exact - // equality instead of substring contains — that avoids cross-account - // collisions like `…account-abc` vs `…account-abcdef`. - let marker = placeholder_marker(account_id); + // Account-unique match. The placeholder URL and the real provider URL + // both carry account-specific fragments, so we can use ends_with and + // avoid substring collisions like `…account-abc` vs `…account-abcdef`. let fragment = target_url_fragment(account_id); - let target = find_page_target_where(&mut cdp, |t| { - t.title == marker || t.url.ends_with(&fragment) - }) - .await?; + let target = + find_page_target_where(&mut cdp, |t| target_matches_account_url(&t.url, account_id)) + .await?; log::info!( "[cdp-session][{}] attaching to target {} url={}", account_id, @@ -232,3 +233,41 @@ async fn run_session_cycle(account_id: &str, real_url: &str) -> Result<(), Strin }) .await } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn placeholder_url_uses_about_blank_fragment_marker() { + assert_eq!( + placeholder_url("acct-42"), + "about:blank#openhuman-acct-acct-42" + ); + } + + #[test] + fn target_match_accepts_placeholder_and_real_provider_fragments_only_for_same_account() { + assert!(target_matches_account_url( + "about:blank#openhuman-acct-acct-42", + "acct-42" + )); + assert!(target_matches_account_url( + "https://discord.com/channels/@me#openhuman-account-acct-42", + "acct-42" + )); + + assert!(!target_matches_account_url( + "about:blank#openhuman-acct-acct-420", + "acct-42" + )); + assert!(!target_matches_account_url( + "https://example.com/openhuman-acct-acct-42", + "acct-42" + )); + assert!(!target_matches_account_url( + "https://discord.com/channels/@me#openhuman-account-acct-420", + "acct-42" + )); + } +} diff --git a/app/src-tauri/src/webview_accounts/mod.rs b/app/src-tauri/src/webview_accounts/mod.rs index c979fb48c..516bcf856 100644 --- a/app/src-tauri/src/webview_accounts/mod.rs +++ b/app/src-tauri/src/webview_accounts/mod.rs @@ -921,18 +921,18 @@ pub async fn webview_account_open( let scanner_url_prefix = format!("{}/", real_url.origin().ascii_serialization()); #[cfg(feature = "cef")] let skip_cdp_for_debug = args.provider == "slack" && !slack_scanner_enabled(); - // Under cef we normally open the webview at a tiny `data:` placeholder - // URL so the CDP session opener can attach and apply the UA override - // BEFORE the real provider URL loads. For Slack debug sessions we allow - // opting out via `OPENHUMAN_DISABLE_SLACK_SCANNER=1`, which also skips - // the long-lived CDP session so external DevTools can attach cleanly. - // Under wry there's no CDP, so navigate straight to the real URL and - // rely on the injected `ua_spoof.js`. + // Under cef we normally open the webview at a tiny placeholder URL so + // the CDP session opener can attach and apply the UA override BEFORE the + // real provider URL loads. For Slack debug sessions we allow opting out + // via `OPENHUMAN_DISABLE_SLACK_SCANNER=1`, which also skips the + // long-lived CDP session so external DevTools can attach cleanly. Under + // wry there's no CDP, so navigate straight to the real URL and rely on + // the injected `ua_spoof.js`. #[cfg(feature = "cef")] let initial_url_str = if skip_cdp_for_debug { real_url_str.clone() } else { - cdp::placeholder_data_url(&args.account_id) + cdp::placeholder_url(&args.account_id) }; #[cfg(not(feature = "cef"))] let initial_url_str = real_url_str.clone();