fix(webview): avoid data url placeholder for child accounts (#884) (#891)

This commit is contained in:
Mega Mind
2026-04-24 09:53:36 -07:00
committed by GitHub
parent 5b3dec4ded
commit 7397d5c114
3 changed files with 75 additions and 36 deletions
+1 -1
View File
@@ -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,
+66 -27
View File
@@ -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"
));
}
}
+8 -8
View File
@@ -921,18 +921,18 @@ pub async fn webview_account_open<R: Runtime>(
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();