compat: persist cookies so consent/login survive restarts, use native UA by default
- tab webviews now share a WebContext backed by the profile dir (~/.local/share/RustyWolf); WebKitGTK keeps cookies in memory unless a persistent store is set, which made youtube.com re-show the consent wall on every launch and made logins impossible to keep - default user agent is WebKit's native one instead of the malformed fake-Chrome + RustyWolf string that pushed sites into Chromium-only code paths; --ua-chrome adds a clean Chrome UA as fallback - --private keeps using an ephemeral in-memory session - README: document --ua-chrome and the persistent profile
This commit is contained in:
parent
fbd88c6281
commit
56f548bd1d
3 changed files with 201 additions and 91 deletions
|
|
@ -44,6 +44,12 @@ pub struct Cli {
|
|||
help = "Linux only: disable WebKit dmabuf renderer (helps some NVIDIA/Wayland setups)"
|
||||
)]
|
||||
pub linux_disable_dmabuf: bool,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
help = "Send a Chrome user agent instead of the native WebKit one (for sites that block WebKit)"
|
||||
)]
|
||||
pub ua_chrome: bool,
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
|
|
|
|||
277
src/webview.rs
277
src/webview.rs
|
|
@ -19,12 +19,11 @@ use tao::platform::unix::WindowExtUnix;
|
|||
use tao::window::{Icon, Window, WindowBuilder};
|
||||
#[cfg(target_os = "linux")]
|
||||
use wry::WebViewBuilderExtUnix;
|
||||
use wry::{PageLoadEvent, Rect, WebView, WebViewBuilder};
|
||||
use wry::{PageLoadEvent, Rect, WebContext, WebView, WebViewBuilder};
|
||||
|
||||
const DEFAULT_HOME: &str = "https://duckduckgo.com";
|
||||
const NEW_TAB_URL: &str = "https://duckduckgo.com";
|
||||
const SEARCH_URL_PREFIX: &str = "https://duckduckgo.com/?q=";
|
||||
const MODERN_BROWSER_UA_BASE: &str = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36";
|
||||
const CHROME_HEIGHT_BASE: f64 = 88.0;
|
||||
const CHROME_HEIGHT_DOWNLOADS_EXTRA: f64 = 104.0;
|
||||
const CHROME_HEIGHT_POPUP_PROMPT_EXTRA: f64 = 44.0;
|
||||
|
|
@ -96,6 +95,8 @@ struct BrowserState {
|
|||
active: usize,
|
||||
next_id: u32,
|
||||
private: bool,
|
||||
user_agent: Option<String>,
|
||||
web_context: Option<WebContext>,
|
||||
}
|
||||
|
||||
struct PendingPopup {
|
||||
|
|
@ -1111,8 +1112,13 @@ pub fn launch_webkit(args: &Cli) -> Result<()> {
|
|||
if args.dry_run {
|
||||
let effective_dmabuf =
|
||||
std::env::var("WEBKIT_DISABLE_DMABUF_RENDERER").unwrap_or_else(|_| "0".to_string());
|
||||
let ua = if args.ua_chrome {
|
||||
chrome_user_agent()
|
||||
} else {
|
||||
"native".to_string()
|
||||
};
|
||||
println!(
|
||||
"engine=webkit url={start_url} private={} linux_backend={:?} linux_disable_dmabuf={} effective_webkit_disable_dmabuf={effective_dmabuf}",
|
||||
"engine=webkit url={start_url} private={} ua={ua} linux_backend={:?} linux_disable_dmabuf={} effective_webkit_disable_dmabuf={effective_dmabuf}",
|
||||
args.private, args.linux_backend, args.linux_disable_dmabuf
|
||||
);
|
||||
return Ok(());
|
||||
|
|
@ -1167,6 +1173,12 @@ pub fn launch_webkit(args: &Cli) -> Result<()> {
|
|||
active: 0,
|
||||
next_id: 1,
|
||||
private: args.private,
|
||||
user_agent: if args.ua_chrome {
|
||||
Some(chrome_user_agent())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
web_context: persistent_web_context(args.private),
|
||||
};
|
||||
|
||||
open_tab(&mut state, &window, &proxy, start_url)?;
|
||||
|
|
@ -1820,71 +1832,20 @@ fn is_child_webview_unsupported_error(err_text: &str) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn build_tab_webview(
|
||||
window: &Window,
|
||||
proxy: &EventLoopProxy<UserEvent>,
|
||||
host_mode: WebViewHostMode,
|
||||
#[cfg(target_os = "linux")] gtk_content_container: Option<>k::Box>,
|
||||
private: bool,
|
||||
user_agent: Option<&str>,
|
||||
mut web_context: Option<&mut WebContext>,
|
||||
tab_id: u32,
|
||||
url: &str,
|
||||
bounds: Rect,
|
||||
) -> Result<(WebView, WebViewHostMode)> {
|
||||
let init_script = format!("{PRIVACY_JS}\n{ADBLOCK_JS}");
|
||||
let user_agent = browser_user_agent();
|
||||
|
||||
let builder = || {
|
||||
let title_proxy = proxy.clone();
|
||||
let page_proxy = proxy.clone();
|
||||
let download_started_proxy = proxy.clone();
|
||||
let download_completed_proxy = proxy.clone();
|
||||
let download_total_proxy = proxy.clone();
|
||||
let new_window_proxy = proxy.clone();
|
||||
|
||||
WebViewBuilder::new()
|
||||
.with_url(url)
|
||||
.with_user_agent(&user_agent)
|
||||
.with_incognito(private)
|
||||
.with_initialization_script(&init_script)
|
||||
.with_bounds(bounds)
|
||||
.with_new_window_req_handler(move |target| {
|
||||
let _ = new_window_proxy.send_event(UserEvent::OpenInNewTab { url: target });
|
||||
false
|
||||
})
|
||||
.with_document_title_changed_handler(move |title| {
|
||||
let _ = title_proxy.send_event(UserEvent::TitleChanged { tab_id, title });
|
||||
})
|
||||
.with_on_page_load_handler(move |event, url| {
|
||||
if matches!(event, PageLoadEvent::Finished) {
|
||||
let _ = page_proxy.send_event(UserEvent::PageLoaded { tab_id, url });
|
||||
}
|
||||
})
|
||||
.with_download_started_handler(move |url, destination| {
|
||||
let path = ensure_unique_download_path(default_download_path_for(&url));
|
||||
if let Some(parent) = path.parent() {
|
||||
let _ = std::fs::create_dir_all(parent);
|
||||
}
|
||||
*destination = path.clone();
|
||||
let path_string = path_to_string(&path);
|
||||
fetch_download_total(
|
||||
url.clone(),
|
||||
path_string.clone(),
|
||||
download_total_proxy.clone(),
|
||||
);
|
||||
let _ = download_started_proxy.send_event(UserEvent::DownloadStarted {
|
||||
url,
|
||||
file_path: path_string,
|
||||
});
|
||||
true
|
||||
})
|
||||
.with_download_completed_handler(move |url, path, success| {
|
||||
let _ = download_completed_proxy.send_event(UserEvent::DownloadCompleted {
|
||||
url,
|
||||
file_path: path.map(|p| path_to_string(&p)),
|
||||
success,
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
match host_mode {
|
||||
WebViewHostMode::Window => {
|
||||
|
|
@ -1893,32 +1854,71 @@ fn build_tab_webview(
|
|||
let container = gtk_content_container.ok_or_else(|| {
|
||||
anyhow!("Failed to create tab webview: missing Linux GTK content container")
|
||||
})?;
|
||||
builder()
|
||||
.build_gtk(container)
|
||||
.map(|webview| (webview, WebViewHostMode::Window))
|
||||
.map_err(|err| anyhow!("Failed to create tab webview with GTK hosting: {err}"))
|
||||
tab_webview_builder(
|
||||
proxy,
|
||||
web_context,
|
||||
private,
|
||||
user_agent,
|
||||
&init_script,
|
||||
tab_id,
|
||||
url,
|
||||
bounds,
|
||||
)
|
||||
.build_gtk(container)
|
||||
.map(|webview| (webview, WebViewHostMode::Window))
|
||||
.map_err(|err| anyhow!("Failed to create tab webview with GTK hosting: {err}"))
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
builder()
|
||||
.build(window)
|
||||
.map(|webview| (webview, WebViewHostMode::Window))
|
||||
.map_err(|err| anyhow!("Failed to create tab webview: {err}"))
|
||||
tab_webview_builder(
|
||||
proxy,
|
||||
web_context,
|
||||
private,
|
||||
user_agent,
|
||||
&init_script,
|
||||
tab_id,
|
||||
url,
|
||||
bounds,
|
||||
)
|
||||
.build(window)
|
||||
.map(|webview| (webview, WebViewHostMode::Window))
|
||||
.map_err(|err| anyhow!("Failed to create tab webview: {err}"))
|
||||
}
|
||||
}
|
||||
WebViewHostMode::Child => match builder().build_as_child(window) {
|
||||
Ok(webview) => Ok((webview, WebViewHostMode::Child)),
|
||||
Err(err) => {
|
||||
let err_text = err.to_string();
|
||||
if is_child_webview_unsupported_error(&err_text) {
|
||||
eprintln!(
|
||||
"child tab webview unsupported on this backend; falling back to window-hosted tabs"
|
||||
);
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if let Some(container) = gtk_content_container {
|
||||
return builder()
|
||||
WebViewHostMode::Child => {
|
||||
let attempt = tab_webview_builder(
|
||||
proxy,
|
||||
web_context.as_deref_mut(),
|
||||
private,
|
||||
user_agent,
|
||||
&init_script,
|
||||
tab_id,
|
||||
url,
|
||||
bounds,
|
||||
)
|
||||
.build_as_child(window);
|
||||
match attempt {
|
||||
Ok(webview) => Ok((webview, WebViewHostMode::Child)),
|
||||
Err(err) => {
|
||||
let err_text = err.to_string();
|
||||
if is_child_webview_unsupported_error(&err_text) {
|
||||
eprintln!(
|
||||
"child tab webview unsupported on this backend; falling back to window-hosted tabs"
|
||||
);
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if let Some(container) = gtk_content_container {
|
||||
return tab_webview_builder(
|
||||
proxy,
|
||||
web_context,
|
||||
private,
|
||||
user_agent,
|
||||
&init_script,
|
||||
tab_id,
|
||||
url,
|
||||
bounds,
|
||||
)
|
||||
.build_gtk(container)
|
||||
.map(|webview| (webview, WebViewHostMode::Window))
|
||||
.map_err(|gtk_err| {
|
||||
|
|
@ -1926,15 +1926,24 @@ fn build_tab_webview(
|
|||
"Failed to create tab webview: child failed ({err_text}); GTK fallback failed ({gtk_err})"
|
||||
)
|
||||
});
|
||||
}
|
||||
return Err(anyhow!(
|
||||
"Failed to create tab webview: child failed ({err_text}); missing Linux GTK content container"
|
||||
));
|
||||
}
|
||||
return Err(anyhow!(
|
||||
"Failed to create tab webview: child failed ({err_text}); missing Linux GTK content container"
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
return builder()
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
return tab_webview_builder(
|
||||
proxy,
|
||||
web_context,
|
||||
private,
|
||||
user_agent,
|
||||
&init_script,
|
||||
tab_id,
|
||||
url,
|
||||
bounds,
|
||||
)
|
||||
.build(window)
|
||||
.map(|webview| (webview, WebViewHostMode::Window))
|
||||
.map_err(|fallback_err| {
|
||||
|
|
@ -1942,27 +1951,109 @@ fn build_tab_webview(
|
|||
"Failed to create tab webview: child failed ({err_text}); window fallback failed ({fallback_err})"
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
Err(anyhow!("Failed to create tab webview: {err_text}"))
|
||||
}
|
||||
Err(anyhow!("Failed to create tab webview: {err_text}"))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn browser_user_agent() -> String {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn tab_webview_builder<'a>(
|
||||
proxy: &EventLoopProxy<UserEvent>,
|
||||
web_context: Option<&'a mut WebContext>,
|
||||
private: bool,
|
||||
user_agent: Option<&str>,
|
||||
init_script: &str,
|
||||
tab_id: u32,
|
||||
url: &str,
|
||||
bounds: Rect,
|
||||
) -> WebViewBuilder<'a> {
|
||||
let title_proxy = proxy.clone();
|
||||
let page_proxy = proxy.clone();
|
||||
let download_started_proxy = proxy.clone();
|
||||
let download_completed_proxy = proxy.clone();
|
||||
let download_total_proxy = proxy.clone();
|
||||
let new_window_proxy = proxy.clone();
|
||||
|
||||
let mut builder = match web_context {
|
||||
Some(web_context) => WebViewBuilder::with_web_context(web_context),
|
||||
None => WebViewBuilder::new(),
|
||||
}
|
||||
.with_url(url)
|
||||
.with_incognito(private)
|
||||
.with_initialization_script(init_script)
|
||||
.with_bounds(bounds);
|
||||
if let Some(user_agent) = user_agent {
|
||||
builder = builder.with_user_agent(user_agent);
|
||||
}
|
||||
builder
|
||||
.with_new_window_req_handler(move |target| {
|
||||
let _ = new_window_proxy.send_event(UserEvent::OpenInNewTab { url: target });
|
||||
false
|
||||
})
|
||||
.with_document_title_changed_handler(move |title| {
|
||||
let _ = title_proxy.send_event(UserEvent::TitleChanged { tab_id, title });
|
||||
})
|
||||
.with_on_page_load_handler(move |event, url| {
|
||||
if matches!(event, PageLoadEvent::Finished) {
|
||||
let _ = page_proxy.send_event(UserEvent::PageLoaded { tab_id, url });
|
||||
}
|
||||
})
|
||||
.with_download_started_handler(move |url, destination| {
|
||||
let path = ensure_unique_download_path(default_download_path_for(&url));
|
||||
if let Some(parent) = path.parent() {
|
||||
let _ = std::fs::create_dir_all(parent);
|
||||
}
|
||||
*destination = path.clone();
|
||||
let path_string = path_to_string(&path);
|
||||
fetch_download_total(
|
||||
url.clone(),
|
||||
path_string.clone(),
|
||||
download_total_proxy.clone(),
|
||||
);
|
||||
let _ = download_started_proxy.send_event(UserEvent::DownloadStarted {
|
||||
url,
|
||||
file_path: path_string,
|
||||
});
|
||||
true
|
||||
})
|
||||
.with_download_completed_handler(move |url, path, success| {
|
||||
let _ = download_completed_proxy.send_event(UserEvent::DownloadCompleted {
|
||||
url,
|
||||
file_path: path.map(|p| path_to_string(&p)),
|
||||
success,
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
fn persistent_web_context(private: bool) -> Option<WebContext> {
|
||||
if private {
|
||||
return None;
|
||||
}
|
||||
// WebKitGTK keeps cookies in memory unless a persistent store is set,
|
||||
// which would log users out and re-show consent walls on every launch.
|
||||
let data_dir = dirs::data_dir()
|
||||
.unwrap_or_else(std::env::temp_dir)
|
||||
.join("RustyWolf");
|
||||
let _ = fs::create_dir_all(&data_dir);
|
||||
Some(WebContext::new(Some(data_dir)))
|
||||
}
|
||||
|
||||
fn chrome_user_agent() -> String {
|
||||
let platform = if cfg!(target_os = "macos") {
|
||||
"Macintosh; Intel Mac OS X 14_0"
|
||||
"Macintosh; Intel Mac OS X 10_15_7"
|
||||
} else if cfg!(target_os = "windows") {
|
||||
"Windows NT 10.0; Win64; x64"
|
||||
} else {
|
||||
"X11; Linux x86_64"
|
||||
};
|
||||
|
||||
// Keep broad compatibility while explicitly identifying RustyWolf.
|
||||
// A consistent, well-formed Chrome UA for sites that refuse to run on WebKit.
|
||||
format!(
|
||||
"Mozilla/5.0 ({platform}; rv:128.0) {MODERN_BROWSER_UA_BASE} RustyWolf/{}",
|
||||
env!("CARGO_PKG_VERSION")
|
||||
"Mozilla/5.0 ({platform}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36"
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1982,6 +2073,8 @@ fn open_tab(
|
|||
#[cfg(target_os = "linux")]
|
||||
state.gtk_content_container.as_ref(),
|
||||
state.private,
|
||||
state.user_agent.as_deref(),
|
||||
state.web_context.as_mut(),
|
||||
tab_id,
|
||||
&url,
|
||||
tab_bounds,
|
||||
|
|
@ -2080,6 +2173,8 @@ fn ensure_tab_loaded(
|
|||
#[cfg(target_os = "linux")]
|
||||
state.gtk_content_container.as_ref(),
|
||||
state.private,
|
||||
state.user_agent.as_deref(),
|
||||
state.web_context.as_mut(),
|
||||
tab_id,
|
||||
&tab_url,
|
||||
tab_bounds,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue