From 4aec3db05b5f0d83ca7242f8b1751cd83f17b5fa Mon Sep 17 00:00:00 2001 From: Laan Tungir Date: Sun, 27 Sep 2026 18:16:48 -0400 Subject: [PATCH] v0.0.8 - Multi-window tab management: Open in New Window moves tabs, per-window active notebook, favicon display, Download Website + Open Link in New Window context menu items; update nostr-core to v0.1.3 --- Cargo.lock | 18 +-- Cargo.toml | 2 +- VERSION | 2 +- src/tab_manager.rs | 366 +++++++++++++++++++++++++++++++++++++-------- src/version.rs | 4 +- src/web_context.rs | 7 + src/webkit_data.rs | 9 +- 7 files changed, 328 insertions(+), 80 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 95ea858..de7fded 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1957,12 +1957,13 @@ checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" [[package]] name = "nostr-core" -version = "0.1.2" +version = "0.1.3" dependencies = [ "aes", "base64", "bech32", "block-modes", + "cbc", "chacha20", "chacha20poly1305", "chrono", @@ -1980,7 +1981,7 @@ dependencies = [ [[package]] name = "nostr-nips" -version = "0.1.2" +version = "0.1.3" dependencies = [ "aes", "block-modes", @@ -2002,7 +2003,7 @@ dependencies = [ [[package]] name = "nostr-relay" -version = "0.1.2" +version = "0.1.3" dependencies = [ "futures-util", "nostr-core", @@ -2020,7 +2021,7 @@ dependencies = [ [[package]] name = "nostr-services" -version = "0.1.2" +version = "0.1.3" dependencies = [ "nostr-core", "nostr-relay", @@ -2035,10 +2036,8 @@ dependencies = [ [[package]] name = "nostr-signer" -version = "0.1.2" +version = "0.1.3" dependencies = [ - "aes", - "cbc", "hex", "libc", "nostr-core", @@ -2046,7 +2045,6 @@ dependencies = [ "serde", "serde_json", "serialport", - "sha2", "thiserror 2.0.20", "tokio", "tracing", @@ -3057,7 +3055,7 @@ dependencies = [ [[package]] name = "sovereign_browser" -version = "0.0.7" +version = "0.0.8" dependencies = [ "anyhow", "base64", @@ -3223,7 +3221,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.3", + "getrandom 0.3.4", "once_cell", "rustix", "windows-sys 0.61.2", diff --git a/Cargo.toml b/Cargo.toml index bcd43ce..ff116c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ members = [ [package] name = "sovereign_browser" -version = "0.0.7" +version = "0.0.8" edition = "2021" license = "MIT" description = "A Linux x86 web browser built on WebKitGTK with Nostr identity" diff --git a/VERSION b/VERSION index 5a5831a..d169b2f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.7 +0.0.8 diff --git a/src/tab_manager.rs b/src/tab_manager.rs index a5de340..25c50ae 100644 --- a/src/tab_manager.rs +++ b/src/tab_manager.rs @@ -63,6 +63,27 @@ static G_MAIN_NOTEBOOK: Lazy>>> = Lazy::new(|| Mutex::new(None)); +/// The currently focused window's notebook. Updated on window focus-in so +/// that "active" operations (new tab, close, reload, …) target the window +/// the user is actually looking at. Falls back to the main notebook. +/// Mirrors the C `g_active_notebook` (tab_manager.c:128). +static G_ACTIVE_NOTEBOOK: Lazy>>> = + Lazy::new(|| Mutex::new(None)); + +/// Auxiliary (non-main) top-level windows, so we can revert the active +/// notebook to the main window when an aux window is destroyed. +/// Mirrors the C `g_aux_windows` (tab_manager.c:1380). +static G_AUX_WINDOWS: Lazy>>> = + Lazy::new(|| Mutex::new(Vec::new())); + +/// Per-tab widget map: tab id → (notebook, webview). GTK widgets are not +/// `Send`, so they are wrapped in ThreadGuards. This lets close/reload/ +/// duplicate resolve the tab's *own* notebook (which may be an auxiliary +/// window's notebook) rather than assuming the main notebook. +static G_TAB_WIDGETS: Lazy< + Mutex, glib::thread_guard::ThreadGuard)>>, +> = Lazy::new(|| Mutex::new(std::collections::HashMap::new())); + /// The custom inspector window (created on `open-window` signal, destroyed /// on `closed`). Stored in a ThreadGuard so it can live in a static. static G_INSPECTOR_WINDOW: Lazy>>> = @@ -101,10 +122,25 @@ pub fn tab_manager_set_suppress_quit_on_last_tab(suppress: bool) { pub fn tab_manager_set_main_window(window: >k::Window, notebook: >k::Notebook) { *G_MAIN_NOTEBOOK.lock().unwrap() = Some(glib::thread_guard::ThreadGuard::new(notebook.clone())); *G_MAIN_WINDOW.lock().unwrap() = Some(glib::thread_guard::ThreadGuard::new(window.clone())); + + // The main window starts as the active window. Focus-in reverts the + // active notebook to the main one when the user returns to it. + set_active_notebook(notebook); + let notebook_focus = notebook.clone(); + window.connect_focus_in_event(move |_w, _e| { + set_active_notebook(¬ebook_focus); + glib::Propagation::Proceed + }); } /// Get the active notebook (focused window's notebook, or main). +/// +/// Mirrors the C `get_effective_notebook()` (tab_manager.c:2765): prefer the +/// focused window's notebook, falling back to the main notebook. pub fn tab_manager_get_active_notebook() -> Option { + if let Some(g) = G_ACTIVE_NOTEBOOK.lock().unwrap().as_ref() { + return Some(g.get_ref().clone()); + } tab_manager_get_main_notebook() } @@ -113,6 +149,49 @@ pub fn tab_manager_get_main_notebook() -> Option { G_MAIN_NOTEBOOK.lock().unwrap().as_ref().map(|g| g.get_ref().clone()) } +/// Record the focused window's notebook as the active one. +fn set_active_notebook(notebook: >k::Notebook) { + *G_ACTIVE_NOTEBOOK.lock().unwrap() = + Some(glib::thread_guard::ThreadGuard::new(notebook.clone())); +} + +/// Revert the active notebook to the main window's notebook. +fn revert_active_notebook_to_main() { + let main = tab_manager_get_main_notebook(); + *G_ACTIVE_NOTEBOOK.lock().unwrap() = + main.map(glib::thread_guard::ThreadGuard::new); +} + +/// Register a tab's notebook + webview so close/reload/duplicate can resolve +/// the tab's own notebook (which may belong to an auxiliary window). +fn register_tab_widgets(tab_id: i32, notebook: >k::Notebook, webview: &WebView) { + G_TAB_WIDGETS.lock().unwrap().insert( + tab_id, + ( + glib::thread_guard::ThreadGuard::new(notebook.clone()), + glib::thread_guard::ThreadGuard::new(webview.clone()), + ), + ); +} + +/// Look up a tab's notebook by id. +fn tab_notebook(tab_id: i32) -> Option { + G_TAB_WIDGETS + .lock() + .unwrap() + .get(&tab_id) + .map(|(nb, _)| nb.get_ref().clone()) +} + +/// Look up a tab's webview by id. +fn tab_webview(tab_id: i32) -> Option { + G_TAB_WIDGETS + .lock() + .unwrap() + .get(&tab_id) + .map(|(_, wv)| wv.get_ref().clone()) +} + /// Get the main window. pub fn tab_manager_get_main_window() -> Option { G_MAIN_WINDOW.lock().unwrap().as_ref().map(|g| g.get_ref().clone()) @@ -407,6 +486,10 @@ pub fn tab_manager_new_tab(notebook: >k::Notebook, ctx: &WebContext, url: Opti page.show_all(); tab_label.show_all(); + // Record the tab's notebook + webview so close/reload/duplicate can + // resolve the tab's own notebook (aux windows have their own notebook). + register_tab_widgets(tab_id, notebook, &webview); + // Apply the current chrome visibility so a tab opened while the // toolbars are hidden stays hidden. Mirrors the C version // (tab_manager.c:3235). The tab strip itself is per-notebook @@ -440,11 +523,23 @@ pub fn tab_manager_new_tab(notebook: >k::Notebook, ctx: &WebContext, url: Opti }); // Favicon change — populate the favicon image from the page favicon. - // (cairo::Surface → Pixbuf conversion is not exposed in this gdk version, - // so we keep the favicon widget but leave it empty.) - let _favicon_clone = favicon.clone(); - webview.connect_favicon_notify(move |_wv| { - // favicon population skipped (API limitation) + // WebKitGTK's favicon database (enabled in web_context) fetches and + // caches favicons; `notify::favicon` fires when one is ready and + // `webview.favicon()` returns a cairo surface. Mirrors the C + // `on_favicon_changed` (tab_manager.c:948). + let favicon_clone = favicon.clone(); + webview.connect_favicon_notify(move |wv| { + if let Some(surface) = wv.favicon() { + if let Ok(img) = cairo::ImageSurface::try_from(surface) { + let w = img.width(); + let h = img.height(); + if w > 0 && h > 0 { + if let Some(pixbuf) = gdk::pixbuf_get_from_surface(&img, 0, 0, w, h) { + favicon_clone.set_from_pixbuf(Some(&pixbuf)); + } + } + } + } }); // Load progress. @@ -662,6 +757,7 @@ pub fn tab_manager_new_tab(notebook: >k::Notebook, ctx: &WebContext, url: Opti None }; + let is_link = hit_test.context_is_link(); if let Some(url) = url { let url_for_action = url.clone(); let nb_clone = notebook_clone_ctx.clone(); @@ -674,7 +770,7 @@ pub fn tab_manager_new_tab(notebook: >k::Notebook, ctx: &WebContext, url: Opti } }); - let label = if hit_test.context_is_link() { + let label = if is_link { "Open Link in New Tab" } else { "Open Page in New Tab" @@ -685,6 +781,29 @@ pub fn tab_manager_new_tab(notebook: >k::Notebook, ctx: &WebContext, url: Opti None, ); context_menu.insert(&item, 0); + + // For links, also add our own "Open Link in New Window" that + // opens a real top-level window (WebKit's stock item fires the + // `create` signal, which we route to a new *tab*). Mirrors the + // C version (tab_manager.c:1995). + if is_link { + let url_for_win = url.clone(); + let nb_win = notebook_clone_ctx.clone(); + let win_action = gio::SimpleAction::new("open-in-new-window", None); + win_action.connect_activate(move |_, _| { + if let Some(wv) = tab_manager_get_active_webview(&nb_win) { + if let Some(ctx) = wv.web_context() { + tab_manager_new_window(Some(&url_for_win), &ctx); + } + } + }); + let win_item = webkit2gtk::ContextMenuItem::from_gaction( + &win_action, + "Open Link in New Window", + None, + ); + context_menu.insert(&win_item, 1); + } } // "Toggle Toolbars" — lets the user restore the hidden chrome @@ -713,15 +832,48 @@ pub fn tab_manager_new_tab(notebook: >k::Notebook, ctx: &WebContext, url: Opti } /// Close a tab by ID. +/// +/// The `notebook` argument is only a fallback: the tab's *own* notebook is +/// resolved from the per-tab widget map, because a tab may live in an +/// auxiliary window's notebook (whose page numbering differs from the main +/// notebook). Mirrors the C `tab_manager_close_tab()` (tab_manager.c:3902). pub fn tab_manager_close_tab(notebook: >k::Notebook, tab_id: i32) { + // Resolve the tab's own notebook (fall back to the passed one). + let nb = tab_notebook(tab_id).unwrap_or_else(|| notebook.clone()); + let mut state = G_TAB_STATE.lock().unwrap(); if let Some(pos) = state.tabs.iter().position(|t| t.id == tab_id) { state.tabs.remove(pos); - if pos < notebook.n_pages() as usize { - notebook.remove_page(Some(pos as u32)); + // Find the page index within the tab's own notebook. + if let Some(wv) = tab_webview(tab_id) { + let n = nb.n_pages(); + for i in 0..n { + if let Some(page) = nb.nth_page(Some(i)) { + if find_webview(&page).map(|w| w == wv).unwrap_or(false) { + nb.remove_page(Some(i)); + break; + } + } + } } - // If no tabs remain and not suppressed, quit. + drop(state); + + // Drop the widget map entry. + G_TAB_WIDGETS.lock().unwrap().remove(&tab_id); + + // If this was an auxiliary window's last tab, close that window. + let main_nb = tab_manager_get_main_notebook(); + let is_main = main_nb.as_ref().map(|m| m == &nb).unwrap_or(false); + if !is_main && nb.n_pages() == 0 { + if let Some(win) = nb.toplevel().and_then(|w| w.downcast::().ok()) { + win.close(); + } + } + + // If no tabs remain anywhere and not suppressed, quit. + let state = G_TAB_STATE.lock().unwrap(); if state.tabs.is_empty() && !state.suppress_quit_on_last_tab { + drop(state); gtk::main_quit(); } } @@ -738,7 +890,24 @@ pub fn tab_manager_close_active() { } /// Get the active tab ID. +/// +/// Resolves the tab from the active notebook's current page (works across +/// windows), falling back to the last-focused tab id. Mirrors the C +/// `tab_manager_get_active()` (tab_manager.c:3993). pub fn tab_manager_get_active_tab() -> i32 { + if let Some(nb) = tab_manager_get_active_notebook() { + let cur = nb.current_page().unwrap_or(0); + if let Some(page) = nb.nth_page(Some(cur)) { + if let Some(wv) = find_webview(&page) { + let widgets = G_TAB_WIDGETS.lock().unwrap(); + for (id, (_, w)) in widgets.iter() { + if w.get_ref() == &wv { + return *id; + } + } + } + } + } let state = G_TAB_STATE.lock().unwrap(); state.active_tab_id } @@ -966,8 +1135,8 @@ pub fn tab_manager_open_file() { /// Create a new blank window. pub fn tab_manager_new_window_blank() { - // Get the context from the main window's active webview. - if let Some(nb) = tab_manager_get_main_notebook() { + // Get the context from the active window's active webview. + if let Some(nb) = tab_manager_get_active_notebook() { if let Some(wv) = tab_manager_get_active_webview(&nb) { if let Some(ctx) = wv.web_context() { tab_manager_new_window(None, &ctx); @@ -979,7 +1148,11 @@ pub fn tab_manager_new_window_blank() { } /// Create a new top-level window with a single tab. -pub fn tab_manager_new_window(url: Option<&str>, ctx: &WebContext) { +/// +/// Returns the new window's webview (or `None` if creation failed), so +/// callers such as "Open in New Window" can close the original tab after +/// the move. Mirrors the C `tab_manager_new_window()` (tab_manager.c:1263). +pub fn tab_manager_new_window(url: Option<&str>, ctx: &WebContext) -> Option { let window = gtk::Window::new(gtk::WindowType::Toplevel); window.set_title("sovereign browser"); window.set_default_size(1024, 768); @@ -995,15 +1168,65 @@ pub fn tab_manager_new_window(url: Option<&str>, ctx: &WebContext) { setup_notebook_action_widgets(¬ebook); // Create the first tab. - tab_manager_new_tab(¬ebook, ctx, url); + let tab_id = tab_manager_new_tab(¬ebook, ctx, url); + if tab_id < 0 { + window.close(); + return None; + } + let webview = tab_webview(tab_id); - // On destroy, do not quit the app (only the main window quits). - window.connect_destroy(|_| { - // no-op: aux window close does not quit + // Focus-in updates the active notebook so "active" operations target + // this window. Mirrors the C `on_window_focus_in` (tab_manager.c:1367). + let notebook_focus = notebook.clone(); + window.connect_focus_in_event(move |_w, _e| { + set_active_notebook(¬ebook_focus); + glib::Propagation::Proceed }); + // On destroy, revert the active notebook to the main window, drop this + // window from the aux list, and remove its tabs from the global state + // (their widgets are gone with the window). Does NOT quit the app. + let window_weak = window.clone(); + let notebook_destroy = notebook.clone(); + window.connect_destroy(move |_| { + let mut aux = G_AUX_WINDOWS.lock().unwrap(); + aux.retain(|g| g.get_ref() != &window_weak); + drop(aux); + + // Remove tabs that belonged to this window's notebook. + let orphan_ids: Vec = { + let widgets = G_TAB_WIDGETS.lock().unwrap(); + widgets + .iter() + .filter(|(_, (nb, _))| nb.get_ref() == ¬ebook_destroy) + .map(|(id, _)| *id) + .collect() + }; + if !orphan_ids.is_empty() { + let mut state = G_TAB_STATE.lock().unwrap(); + state.tabs.retain(|t| !orphan_ids.contains(&t.id)); + drop(state); + let mut widgets = G_TAB_WIDGETS.lock().unwrap(); + for id in &orphan_ids { + widgets.remove(id); + } + } + + revert_active_notebook_to_main(); + }); + + // Register this window as an auxiliary window. + G_AUX_WINDOWS + .lock() + .unwrap() + .push(glib::thread_guard::ThreadGuard::new(window.clone())); + + // This new window is now the active one. + set_active_notebook(¬ebook); + window.show_all(); window.present(); + webview } /// Show the tab context menu (right-click on a tab label). @@ -1046,30 +1269,15 @@ fn show_tab_context_menu(notebook: >k::Notebook, tab_id: i32, event: &gdk::Eve menu.append(>k::SeparatorMenuItem::new()); let item_new_win = gtk::MenuItem::with_label("Open in New Window"); - let notebook_clone_nw = notebook.clone(); item_new_win.connect_activate(move |_| { - let state = G_TAB_STATE.lock().unwrap(); - let url = state.tabs.iter().find(|t| t.id == tab_id).map(|t| t.url.clone()); - drop(state); - if let Some(wv) = tab_manager_get_active_webview(¬ebook_clone_nw) { - if let Some(ctx) = wv.web_context() { - tab_manager_new_window(url.as_deref(), &ctx); - } - } + // Moves the tab to a new window (closes the original). + tab_manager_open_in_new_window(tab_id); }); menu.append(&item_new_win); let item_dup = gtk::MenuItem::with_label("Duplicate Tab"); - let notebook_clone5 = notebook.clone(); item_dup.connect_activate(move |_| { - let state = G_TAB_STATE.lock().unwrap(); - let url = state.tabs.iter().find(|t| t.id == tab_id).map(|t| t.url.clone()); - drop(state); - if let Some(wv) = tab_manager_get_active_webview(¬ebook_clone5) { - if let Some(ctx) = wv.web_context() { - tab_manager_new_tab(¬ebook_clone5, &ctx, url.as_deref()); - } - } + tab_manager_duplicate(tab_id); }); menu.append(&item_dup); @@ -1079,6 +1287,23 @@ fn show_tab_context_menu(notebook: >k::Notebook, tab_id: i32, event: &gdk::Eve }); menu.append(&item_reload); + menu.append(>k::SeparatorMenuItem::new()); + + // Download Website — mirrors the C context menu (tab_manager.c:818). + let item_dl = gtk::MenuItem::with_label("Download Website"); + item_dl.connect_activate(move |_| { + let url = tab_webview(tab_id) + .and_then(|wv| wv.uri().map(|u| u.to_string())) + .unwrap_or_default(); + if !url.is_empty() { + let _ = crate::site_downloader::site_downloader_download( + &url, + "/tmp/sovereign_download", + ); + } + }); + menu.append(&item_dl); + menu.show_all(); menu.popup_at_pointer(Some(event)); } @@ -1109,51 +1334,62 @@ pub fn tab_manager_close_to_right(notebook: >k::Notebook, tab_id: i32) { /// Reload a specific tab by ID. pub fn tab_manager_reload_tab(tab_id: i32) { - if let Some(nb) = tab_manager_get_active_notebook() { - let n = nb.n_pages(); - for i in 0..n { - if let Some(page) = nb.nth_page(Some(i)) { - if let Some(wv) = find_webview(&page) { - // Match by tab id via the state. - let state = G_TAB_STATE.lock().unwrap(); - let matches = state.tabs.get(i as usize).map(|t| t.id == tab_id).unwrap_or(false); - drop(state); - if matches { - wv.reload(); - return; - } - } - } - } + if let Some(wv) = tab_webview(tab_id) { + wv.reload_bypass_cache(); } } -/// Duplicate a tab by ID. +/// Duplicate a tab by ID (opens the same URL in a new tab in the same window). pub fn tab_manager_duplicate(tab_id: i32) { let state = G_TAB_STATE.lock().unwrap(); let url = state.tabs.iter().find(|t| t.id == tab_id).map(|t| t.url.clone()); drop(state); - if let Some(nb) = tab_manager_get_active_notebook() { - if let Some(wv) = tab_manager_get_active_webview(&nb) { - if let Some(ctx) = wv.web_context() { - tab_manager_new_tab(&nb, &ctx, url.as_deref()); - } + // Duplicate into the tab's own notebook (its window). + let nb = match tab_notebook(tab_id) { + Some(n) => n, + None => return, + }; + if let Some(wv) = tab_webview(tab_id) { + if let Some(ctx) = wv.web_context() { + tab_manager_new_tab(&nb, &ctx, url.as_deref()); } } } /// Open a tab in a new window by ID. +/// +/// This *moves* the tab: the URL is opened in a brand-new top-level window +/// and the original tab is closed. Mirrors the C +/// `tab_manager_open_in_new_window()` (tab_manager.c:4141). pub fn tab_manager_open_in_new_window(tab_id: i32) { - let state = G_TAB_STATE.lock().unwrap(); - let url = state.tabs.iter().find(|t| t.id == tab_id).map(|t| t.url.clone()); - drop(state); - if let Some(nb) = tab_manager_get_active_notebook() { - if let Some(wv) = tab_manager_get_active_webview(&nb) { - if let Some(ctx) = wv.web_context() { - tab_manager_new_window(url.as_deref(), &ctx); - } - } + // Capture the URL and the tab's own notebook before moving. + let (url, nb) = { + let state = G_TAB_STATE.lock().unwrap(); + let url = state.tabs.iter().find(|t| t.id == tab_id).map(|t| t.url.clone()); + (url, tab_notebook(tab_id)) + }; + let nb = match nb { + Some(n) => n, + None => return, + }; + + // Get a WebContext from the tab's webview (or the notebook's active one). + let ctx = tab_webview(tab_id) + .and_then(|wv| wv.web_context()) + .or_else(|| tab_manager_get_active_webview(&nb).and_then(|wv| wv.web_context())); + let ctx = match ctx { + Some(c) => c, + None => return, + }; + + // Open the URL in a new window. If creation fails, keep the original tab. + if tab_manager_new_window(url.as_deref(), &ctx).is_none() { + return; } + + // Close the original tab — "Open in New Window" moves the tab rather + // than duplicating it. + tab_manager_close_tab(&nb, tab_id); } /// Build the hamburger menu button. diff --git a/src/version.rs b/src/version.rs index ff9bca8..2124ecd 100644 --- a/src/version.rs +++ b/src/version.rs @@ -1,7 +1,7 @@ //! Version information for sovereign_browser /// The current version of sovereign_browser (with leading 'v'). -pub const VERSION: &str = "v0.0.7"; +pub const VERSION: &str = "v0.0.8"; /// Major version number. pub const VERSION_MAJOR: u32 = 0; @@ -10,4 +10,4 @@ pub const VERSION_MAJOR: u32 = 0; pub const VERSION_MINOR: u32 = 0; /// Patch version number. -pub const VERSION_PATCH: u32 = 7; +pub const VERSION_PATCH: u32 = 8; diff --git a/src/web_context.rs b/src/web_context.rs index 9573a51..72e0f49 100644 --- a/src/web_context.rs +++ b/src/web_context.rs @@ -27,6 +27,13 @@ pub fn web_context_create() -> WebContext { sec_mgr.register_uri_scheme_as_secure("nostr"); } + // Enable the favicon database so `notify::favicon` fires and + // `webview.favicon()` returns a surface. Without a directory the + // database is disabled and tab favicons never appear. Mirrors the C + // `webkit_web_context_set_favicon_database_directory()` (web_context.c:237). + let fav_dir = std::env::temp_dir().join("sovereign_browser-favicons"); + ctx.set_favicon_database_directory(fav_dir.to_str()); + ctx } diff --git a/src/webkit_data.rs b/src/webkit_data.rs index 7dc435f..3359796 100644 --- a/src/webkit_data.rs +++ b/src/webkit_data.rs @@ -12,7 +12,7 @@ pub fn webkit_data_get_dir() -> PathBuf { } /// Configure the website data manager for a profile. -pub fn webkit_data_configure(_web_context: &WebContext, pubkey_hex: &str) { +pub fn webkit_data_configure(web_context: &WebContext, pubkey_hex: &str) { let data_dir = webkit_data_get_dir().join(pubkey_hex); std::fs::create_dir_all(&data_dir).ok(); @@ -24,4 +24,11 @@ pub fn webkit_data_configure(_web_context: &WebContext, pubkey_hex: &str) { let _manager = WebsiteDataManager::builder() .local_storage_directory(data_dir.join("localstorage").to_str().unwrap_or("")) .build(); + + // Per-user favicon database so a switched user doesn't see the previous + // user's visited-site icons in the tab strip. Mirrors the C + // `webkit_web_context_set_favicon_database_directory()` (web_context.c:237). + let fav_dir = data_dir.join("favicons"); + std::fs::create_dir_all(&fav_dir).ok(); + web_context.set_favicon_database_directory(fav_dir.to_str()); }