Fix tab_zoom borrow and remove unused Ordering import
Browse files- src-tauri/src/browser/commands.rs +28 -135
src-tauri/src/browser/commands.rs
CHANGED
|
@@ -1,5 +1,3 @@
|
|
| 1 |
-
use std::sync::atomic::Ordering;
|
| 2 |
-
|
| 3 |
use tauri::webview::{PageLoadEvent, WebviewBuilder};
|
| 4 |
use tauri::{AppHandle, LogicalPosition, LogicalSize, Manager, Url, WebviewUrl};
|
| 5 |
|
|
@@ -10,8 +8,6 @@ use crate::adblock::engine::AdBlockState;
|
|
| 10 |
use crate::adblock::scripts;
|
| 11 |
use crate::state::AppState;
|
| 12 |
|
| 13 |
-
// ═══ COMMANDS ═══
|
| 14 |
-
|
| 15 |
#[tauri::command]
|
| 16 |
pub async fn browser_init(app: AppHandle, layout: ViewportLayout) -> Result<BrowserSnapshot, String> {
|
| 17 |
let existing = snapshot(&app)?;
|
|
@@ -72,14 +68,9 @@ pub async fn tab_close(app: AppHandle, tab_id: String, layout: ViewportLayout) -
|
|
| 72 |
let state = app.state::<AppState>();
|
| 73 |
let mut tabs = state.tabs.lock().map_err(|_| "lock")?;
|
| 74 |
let label = tabs.tabs.get(&tab_id).map(|t| t.label.clone());
|
| 75 |
-
|
| 76 |
-
if let Some(tab) = tabs.tabs.remove(&tab_id) {
|
| 77 |
-
tabs.push_closed(tab);
|
| 78 |
-
}
|
| 79 |
tabs.order.retain(|id| id != &tab_id);
|
| 80 |
-
if tabs.active.as_deref() == Some(&tab_id) {
|
| 81 |
-
tabs.active = tabs.order.last().cloned();
|
| 82 |
-
}
|
| 83 |
label
|
| 84 |
};
|
| 85 |
if let Some(label) = label {
|
|
@@ -95,7 +86,6 @@ pub async fn tab_close(app: AppHandle, tab_id: String, layout: ViewportLayout) -
|
|
| 95 |
snapshot(&app)
|
| 96 |
}
|
| 97 |
|
| 98 |
-
/// Restore most recently closed tab
|
| 99 |
#[tauri::command]
|
| 100 |
pub async fn tab_restore(app: AppHandle, layout: ViewportLayout) -> Result<BrowserSnapshot, String> {
|
| 101 |
let url = {
|
|
@@ -103,9 +93,7 @@ pub async fn tab_restore(app: AppHandle, layout: ViewportLayout) -> Result<Brows
|
|
| 103 |
let mut tabs = state.tabs.lock().map_err(|_| "lock")?;
|
| 104 |
tabs.pop_closed().map(|t| t.url)
|
| 105 |
};
|
| 106 |
-
if let Some(url) = url {
|
| 107 |
-
create_tab_inner(&app, &url, &layout).await?;
|
| 108 |
-
}
|
| 109 |
snapshot(&app)
|
| 110 |
}
|
| 111 |
|
|
@@ -119,7 +107,7 @@ pub async fn tab_navigate(app: AppHandle, tab_id: String, url: String) -> Result
|
|
| 119 |
let tab = tabs.tabs.get_mut(&tab_id).ok_or("tab not found")?;
|
| 120 |
tab.url = resolved.clone();
|
| 121 |
tab.loading = true;
|
| 122 |
-
tab.can_go_back = true;
|
| 123 |
tab.label.clone()
|
| 124 |
};
|
| 125 |
let webview = app.get_webview(&label).ok_or("webview not found")?;
|
|
@@ -152,17 +140,19 @@ pub async fn tab_forward(app: AppHandle, tab_id: String) -> Result<(), String> {
|
|
| 152 |
#[tauri::command]
|
| 153 |
pub async fn tab_zoom(app: AppHandle, tab_id: String, zoom: f64) -> Result<BrowserSnapshot, String> {
|
| 154 |
let zoom = zoom.clamp(0.5, 2.0);
|
| 155 |
-
let
|
| 156 |
let state = app.state::<AppState>();
|
| 157 |
let mut tabs = state.tabs.lock().map_err(|_| "lock")?;
|
| 158 |
-
let
|
| 159 |
-
|
| 160 |
-
|
|
|
|
|
|
|
|
|
|
| 161 |
tabs.remember_zoom(&domain, zoom);
|
| 162 |
-
|
| 163 |
};
|
| 164 |
app.get_webview(&label).ok_or("webview not found")?.set_zoom(zoom).map_err(|e| e.to_string())?;
|
| 165 |
-
// Persist zoom memory
|
| 166 |
let state = app.state::<AppState>();
|
| 167 |
if let Ok(tabs) = state.tabs.lock() {
|
| 168 |
let _ = crate::persistence::save_json(&app, "zoom_memory.json", &tabs.zoom_memory);
|
|
@@ -171,178 +161,81 @@ pub async fn tab_zoom(app: AppHandle, tab_id: String, zoom: f64) -> Result<Brows
|
|
| 171 |
}
|
| 172 |
|
| 173 |
#[tauri::command]
|
| 174 |
-
pub async fn tab_resize(app: AppHandle, layout: ViewportLayout) -> Result<(), String> {
|
| 175 |
-
resize_active(&app, &layout)
|
| 176 |
-
}
|
| 177 |
|
| 178 |
#[tauri::command]
|
| 179 |
-
pub fn tab_get_all(app: AppHandle) -> Result<BrowserSnapshot, String> {
|
| 180 |
-
snapshot(&app)
|
| 181 |
-
}
|
| 182 |
|
| 183 |
-
/// Toggle pinned state
|
| 184 |
#[tauri::command]
|
| 185 |
pub fn tab_pin(app: AppHandle, tab_id: String, pinned: bool) -> Result<BrowserSnapshot, String> {
|
| 186 |
update_tab_field(&app, &tab_id, |t| { t.pinned = pinned; });
|
| 187 |
snapshot(&app)
|
| 188 |
}
|
| 189 |
|
| 190 |
-
/// Find in page — inject JS to highlight matches
|
| 191 |
#[tauri::command]
|
| 192 |
pub async fn tab_find(app: AppHandle, tab_id: String, query: String) -> Result<u32, String> {
|
| 193 |
-
let js = format!(r#"
|
| 194 |
-
(function() {{
|
| 195 |
-
window.__muse_find_cleanup && window.__muse_find_cleanup();
|
| 196 |
-
if (!{query_json}) return 0;
|
| 197 |
-
const text = {query_json};
|
| 198 |
-
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
|
| 199 |
-
let count = 0;
|
| 200 |
-
const marks = [];
|
| 201 |
-
while (walker.nextNode()) {{
|
| 202 |
-
const node = walker.currentNode;
|
| 203 |
-
const idx = node.textContent.toLowerCase().indexOf(text.toLowerCase());
|
| 204 |
-
if (idx >= 0) {{
|
| 205 |
-
const range = document.createRange();
|
| 206 |
-
range.setStart(node, idx);
|
| 207 |
-
range.setEnd(node, idx + text.length);
|
| 208 |
-
const mark = document.createElement('mark');
|
| 209 |
-
mark.style.cssText = 'background:#C49A3C;color:#100E0B;border-radius:2px;padding:0 1px';
|
| 210 |
-
range.surroundContents(mark);
|
| 211 |
-
marks.push(mark);
|
| 212 |
-
count++;
|
| 213 |
-
}}
|
| 214 |
-
}}
|
| 215 |
-
if (marks.length > 0) marks[0].scrollIntoView({{ block: 'center' }});
|
| 216 |
-
window.__muse_find_cleanup = () => marks.forEach(m => {{
|
| 217 |
-
const parent = m.parentNode;
|
| 218 |
-
if (parent) {{ parent.replaceChild(document.createTextNode(m.textContent || ''), m); parent.normalize(); }}
|
| 219 |
-
}});
|
| 220 |
-
return count;
|
| 221 |
-
}})()
|
| 222 |
-
"#, query_json = serde_json::to_string(&query).unwrap_or("\"\"".to_string()));
|
| 223 |
eval_on_tab(&app, &tab_id, &js)?;
|
| 224 |
-
Ok(0)
|
| 225 |
}
|
| 226 |
|
| 227 |
-
/// Clear find highlights
|
| 228 |
#[tauri::command]
|
| 229 |
pub async fn tab_find_clear(app: AppHandle, tab_id: String) -> Result<(), String> {
|
| 230 |
eval_on_tab(&app, &tab_id, "window.__muse_find_cleanup && window.__muse_find_cleanup()")
|
| 231 |
}
|
| 232 |
|
| 233 |
-
// ═══ INTERNAL ═══
|
| 234 |
-
|
| 235 |
pub(crate) async fn create_tab_inner(app: &AppHandle, url: &str, layout: &ViewportLayout) -> Result<String, String> {
|
| 236 |
-
let id_num = app.state::<AppState>().next_tab_id.fetch_add(1, Ordering::Relaxed);
|
| 237 |
let id = format!("tab-{id_num}");
|
| 238 |
let label = format!("muse-tab-{id_num}");
|
| 239 |
let parsed = Url::parse(url).map_err(|e| e.to_string())?;
|
| 240 |
-
|
| 241 |
let init_script = scripts::build_init_script(&scripts::blocked_domains_json());
|
| 242 |
-
// Add favicon extraction + navigation state tracking to init script
|
| 243 |
let full_init = format!("{init_script}\n{}", FAVICON_AND_NAV_SCRIPT);
|
| 244 |
-
|
| 245 |
let app_for_load = app.clone();
|
| 246 |
let id_for_load = id.clone();
|
| 247 |
let app_for_title = app.clone();
|
| 248 |
let id_for_title = id.clone();
|
| 249 |
let app_for_cosmetic = app.clone();
|
| 250 |
-
|
| 251 |
let builder = WebviewBuilder::new(label.clone(), WebviewUrl::External(parsed))
|
| 252 |
.initialization_script(&full_init)
|
| 253 |
.on_page_load(move |webview, payload| {
|
| 254 |
let url = payload.url().to_string();
|
| 255 |
let loading = matches!(payload.event(), PageLoadEvent::Started);
|
| 256 |
-
update_tab_field(&app_for_load, &id_for_load, |t| {
|
| 257 |
-
t.url = url.clone();
|
| 258 |
-
t.loading = loading;
|
| 259 |
-
});
|
| 260 |
if matches!(payload.event(), PageLoadEvent::Finished) {
|
| 261 |
let css = app_for_cosmetic.state::<AdBlockState>().get_cosmetic_css(&url);
|
| 262 |
if !css.is_empty() {
|
| 263 |
let escaped = css.replace('\\', "\\\\").replace('`', "\\`");
|
| 264 |
-
let inject = format!(
|
| 265 |
-
"(function(){{const s=document.createElement('style');s.id='__muse_shield';s.textContent=`{escaped}`;document.head.appendChild(s)}})();"
|
| 266 |
-
);
|
| 267 |
let _ = webview.eval(&inject);
|
| 268 |
}
|
| 269 |
-
// Request favicon from page
|
| 270 |
let _ = webview.eval("window.__muse_report_favicon && window.__muse_report_favicon()");
|
| 271 |
}
|
| 272 |
})
|
| 273 |
-
.on_document_title_changed({
|
| 274 |
-
|
| 275 |
-
let id_t = id_for_title;
|
| 276 |
-
move |_webview, title| {
|
| 277 |
-
update_tab_field(&app_t, &id_t, |t| {
|
| 278 |
-
if !title.trim().is_empty() { t.title = title.clone(); }
|
| 279 |
-
});
|
| 280 |
-
}
|
| 281 |
})
|
| 282 |
-
.on_navigation({
|
| 283 |
-
let app_nav = app.clone();
|
| 284 |
-
move |url| { !navigation_blocked(&app_nav, url.as_str()) }
|
| 285 |
-
});
|
| 286 |
-
|
| 287 |
let window = app.get_window("main").ok_or("main window not found")?;
|
| 288 |
let (x, y, w, h) = bounds(layout);
|
| 289 |
-
window.add_child(builder, LogicalPosition::new(x, y), LogicalSize::new(w, h))
|
| 290 |
-
.map_err(|e| e.to_string())?;
|
| 291 |
-
|
| 292 |
-
// Check zoom memory for this domain
|
| 293 |
let domain = extract_domain(url);
|
| 294 |
-
let saved_zoom = {
|
| 295 |
-
let state = app.state::<AppState>();
|
| 296 |
-
let tabs = state.tabs.lock().map_err(|_| "lock")?;
|
| 297 |
-
tabs.get_zoom_for_domain(&domain)
|
| 298 |
-
};
|
| 299 |
-
|
| 300 |
let now = chrono::Utc::now().timestamp();
|
| 301 |
let zoom = saved_zoom.unwrap_or(1.0);
|
| 302 |
-
|
| 303 |
let previous = {
|
| 304 |
let state = app.state::<AppState>();
|
| 305 |
let mut tabs = state.tabs.lock().map_err(|_| "lock")?;
|
| 306 |
let previous = tabs.active.clone();
|
| 307 |
tabs.active = Some(id.clone());
|
| 308 |
tabs.order.push(id.clone());
|
| 309 |
-
tabs.tabs.insert(id.clone(), BrowserTab {
|
| 310 |
-
id: id.clone(), label: label.clone(), url: url.to_string(),
|
| 311 |
-
title: "New Tab".to_string(), favicon: None, loading: true,
|
| 312 |
-
pinned: false, sleeping: false, zoom, can_go_back: false,
|
| 313 |
-
can_go_forward: false, last_active: now,
|
| 314 |
-
});
|
| 315 |
previous
|
| 316 |
};
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
if zoom != 1.0 {
|
| 320 |
-
if let Some(webview) = app.get_webview(&label) {
|
| 321 |
-
let _ = webview.set_zoom(zoom);
|
| 322 |
-
}
|
| 323 |
-
}
|
| 324 |
-
|
| 325 |
-
if let Some(prev) = previous {
|
| 326 |
-
if prev != id { hide_tab(app, &prev)?; }
|
| 327 |
-
}
|
| 328 |
emit_snapshot(app)?;
|
| 329 |
Ok(id)
|
| 330 |
}
|
| 331 |
|
| 332 |
-
|
| 333 |
-
const FAVICON_AND_NAV_SCRIPT: &str = r#"
|
| 334 |
-
(function() {
|
| 335 |
-
window.__muse_report_favicon = function() {
|
| 336 |
-
const link = document.querySelector('link[rel~="icon"], link[rel="shortcut icon"], link[rel="apple-touch-icon"]');
|
| 337 |
-
if (link && link.href) {
|
| 338 |
-
try { window.__TAURI_INTERNALS__.invoke('__tab_favicon', { favicon: link.href }); } catch {}
|
| 339 |
-
}
|
| 340 |
-
};
|
| 341 |
-
// Report on DOMContentLoaded
|
| 342 |
-
if (document.readyState === 'loading') {
|
| 343 |
-
document.addEventListener('DOMContentLoaded', window.__muse_report_favicon);
|
| 344 |
-
} else {
|
| 345 |
-
window.__muse_report_favicon();
|
| 346 |
-
}
|
| 347 |
-
})();
|
| 348 |
-
"#;
|
|
|
|
|
|
|
|
|
|
| 1 |
use tauri::webview::{PageLoadEvent, WebviewBuilder};
|
| 2 |
use tauri::{AppHandle, LogicalPosition, LogicalSize, Manager, Url, WebviewUrl};
|
| 3 |
|
|
|
|
| 8 |
use crate::adblock::scripts;
|
| 9 |
use crate::state::AppState;
|
| 10 |
|
|
|
|
|
|
|
| 11 |
#[tauri::command]
|
| 12 |
pub async fn browser_init(app: AppHandle, layout: ViewportLayout) -> Result<BrowserSnapshot, String> {
|
| 13 |
let existing = snapshot(&app)?;
|
|
|
|
| 68 |
let state = app.state::<AppState>();
|
| 69 |
let mut tabs = state.tabs.lock().map_err(|_| "lock")?;
|
| 70 |
let label = tabs.tabs.get(&tab_id).map(|t| t.label.clone());
|
| 71 |
+
if let Some(tab) = tabs.tabs.remove(&tab_id) { tabs.push_closed(tab); }
|
|
|
|
|
|
|
|
|
|
| 72 |
tabs.order.retain(|id| id != &tab_id);
|
| 73 |
+
if tabs.active.as_deref() == Some(&tab_id) { tabs.active = tabs.order.last().cloned(); }
|
|
|
|
|
|
|
| 74 |
label
|
| 75 |
};
|
| 76 |
if let Some(label) = label {
|
|
|
|
| 86 |
snapshot(&app)
|
| 87 |
}
|
| 88 |
|
|
|
|
| 89 |
#[tauri::command]
|
| 90 |
pub async fn tab_restore(app: AppHandle, layout: ViewportLayout) -> Result<BrowserSnapshot, String> {
|
| 91 |
let url = {
|
|
|
|
| 93 |
let mut tabs = state.tabs.lock().map_err(|_| "lock")?;
|
| 94 |
tabs.pop_closed().map(|t| t.url)
|
| 95 |
};
|
| 96 |
+
if let Some(url) = url { create_tab_inner(&app, &url, &layout).await?; }
|
|
|
|
|
|
|
| 97 |
snapshot(&app)
|
| 98 |
}
|
| 99 |
|
|
|
|
| 107 |
let tab = tabs.tabs.get_mut(&tab_id).ok_or("tab not found")?;
|
| 108 |
tab.url = resolved.clone();
|
| 109 |
tab.loading = true;
|
| 110 |
+
tab.can_go_back = true;
|
| 111 |
tab.label.clone()
|
| 112 |
};
|
| 113 |
let webview = app.get_webview(&label).ok_or("webview not found")?;
|
|
|
|
| 140 |
#[tauri::command]
|
| 141 |
pub async fn tab_zoom(app: AppHandle, tab_id: String, zoom: f64) -> Result<BrowserSnapshot, String> {
|
| 142 |
let zoom = zoom.clamp(0.5, 2.0);
|
| 143 |
+
let label = {
|
| 144 |
let state = app.state::<AppState>();
|
| 145 |
let mut tabs = state.tabs.lock().map_err(|_| "lock")?;
|
| 146 |
+
let (label, domain) = {
|
| 147 |
+
let tab = tabs.tabs.get_mut(&tab_id).ok_or("tab not found")?;
|
| 148 |
+
tab.zoom = zoom;
|
| 149 |
+
let domain = extract_domain(&tab.url);
|
| 150 |
+
(tab.label.clone(), domain)
|
| 151 |
+
};
|
| 152 |
tabs.remember_zoom(&domain, zoom);
|
| 153 |
+
label
|
| 154 |
};
|
| 155 |
app.get_webview(&label).ok_or("webview not found")?.set_zoom(zoom).map_err(|e| e.to_string())?;
|
|
|
|
| 156 |
let state = app.state::<AppState>();
|
| 157 |
if let Ok(tabs) = state.tabs.lock() {
|
| 158 |
let _ = crate::persistence::save_json(&app, "zoom_memory.json", &tabs.zoom_memory);
|
|
|
|
| 161 |
}
|
| 162 |
|
| 163 |
#[tauri::command]
|
| 164 |
+
pub async fn tab_resize(app: AppHandle, layout: ViewportLayout) -> Result<(), String> { resize_active(&app, &layout) }
|
|
|
|
|
|
|
| 165 |
|
| 166 |
#[tauri::command]
|
| 167 |
+
pub fn tab_get_all(app: AppHandle) -> Result<BrowserSnapshot, String> { snapshot(&app) }
|
|
|
|
|
|
|
| 168 |
|
|
|
|
| 169 |
#[tauri::command]
|
| 170 |
pub fn tab_pin(app: AppHandle, tab_id: String, pinned: bool) -> Result<BrowserSnapshot, String> {
|
| 171 |
update_tab_field(&app, &tab_id, |t| { t.pinned = pinned; });
|
| 172 |
snapshot(&app)
|
| 173 |
}
|
| 174 |
|
|
|
|
| 175 |
#[tauri::command]
|
| 176 |
pub async fn tab_find(app: AppHandle, tab_id: String, query: String) -> Result<u32, String> {
|
| 177 |
+
let js = format!(r#"(function(){{window.__muse_find_cleanup&&window.__muse_find_cleanup();if(!{q})return 0;const text={q};const walker=document.createTreeWalker(document.body,NodeFilter.SHOW_TEXT);let count=0;const marks=[];while(walker.nextNode()){{const node=walker.currentNode;const idx=node.textContent.toLowerCase().indexOf(text.toLowerCase());if(idx>=0){{const range=document.createRange();range.setStart(node,idx);range.setEnd(node,idx+text.length);const mark=document.createElement('mark');mark.style.cssText='background:#C49A3C;color:#100E0B;border-radius:2px;padding:0 1px';range.surroundContents(mark);marks.push(mark);count++;}}}}if(marks.length>0)marks[0].scrollIntoView({{block:'center'}});window.__muse_find_cleanup=()=>marks.forEach(m=>{{const p=m.parentNode;if(p){{p.replaceChild(document.createTextNode(m.textContent||''),m);p.normalize();}}}});return count;}})()"#, q=serde_json::to_string(&query).unwrap_or("\"\"".to_string()));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
eval_on_tab(&app, &tab_id, &js)?;
|
| 179 |
+
Ok(0)
|
| 180 |
}
|
| 181 |
|
|
|
|
| 182 |
#[tauri::command]
|
| 183 |
pub async fn tab_find_clear(app: AppHandle, tab_id: String) -> Result<(), String> {
|
| 184 |
eval_on_tab(&app, &tab_id, "window.__muse_find_cleanup && window.__muse_find_cleanup()")
|
| 185 |
}
|
| 186 |
|
|
|
|
|
|
|
| 187 |
pub(crate) async fn create_tab_inner(app: &AppHandle, url: &str, layout: &ViewportLayout) -> Result<String, String> {
|
| 188 |
+
let id_num = app.state::<AppState>().next_tab_id.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
| 189 |
let id = format!("tab-{id_num}");
|
| 190 |
let label = format!("muse-tab-{id_num}");
|
| 191 |
let parsed = Url::parse(url).map_err(|e| e.to_string())?;
|
|
|
|
| 192 |
let init_script = scripts::build_init_script(&scripts::blocked_domains_json());
|
|
|
|
| 193 |
let full_init = format!("{init_script}\n{}", FAVICON_AND_NAV_SCRIPT);
|
|
|
|
| 194 |
let app_for_load = app.clone();
|
| 195 |
let id_for_load = id.clone();
|
| 196 |
let app_for_title = app.clone();
|
| 197 |
let id_for_title = id.clone();
|
| 198 |
let app_for_cosmetic = app.clone();
|
|
|
|
| 199 |
let builder = WebviewBuilder::new(label.clone(), WebviewUrl::External(parsed))
|
| 200 |
.initialization_script(&full_init)
|
| 201 |
.on_page_load(move |webview, payload| {
|
| 202 |
let url = payload.url().to_string();
|
| 203 |
let loading = matches!(payload.event(), PageLoadEvent::Started);
|
| 204 |
+
update_tab_field(&app_for_load, &id_for_load, |t| { t.url = url.clone(); t.loading = loading; });
|
|
|
|
|
|
|
|
|
|
| 205 |
if matches!(payload.event(), PageLoadEvent::Finished) {
|
| 206 |
let css = app_for_cosmetic.state::<AdBlockState>().get_cosmetic_css(&url);
|
| 207 |
if !css.is_empty() {
|
| 208 |
let escaped = css.replace('\\', "\\\\").replace('`', "\\`");
|
| 209 |
+
let inject = format!("(function(){{const s=document.createElement('style');s.id='__muse_shield';s.textContent=`{escaped}`;document.head.appendChild(s)}})();");
|
|
|
|
|
|
|
| 210 |
let _ = webview.eval(&inject);
|
| 211 |
}
|
|
|
|
| 212 |
let _ = webview.eval("window.__muse_report_favicon && window.__muse_report_favicon()");
|
| 213 |
}
|
| 214 |
})
|
| 215 |
+
.on_document_title_changed(move |_webview, title| {
|
| 216 |
+
update_tab_field(&app_for_title, &id_for_title, |t| { if !title.trim().is_empty() { t.title = title.clone(); } });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
})
|
| 218 |
+
.on_navigation({ let app_nav = app.clone(); move |url| { !navigation_blocked(&app_nav, url.as_str()) } });
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
let window = app.get_window("main").ok_or("main window not found")?;
|
| 220 |
let (x, y, w, h) = bounds(layout);
|
| 221 |
+
window.add_child(builder, LogicalPosition::new(x, y), LogicalSize::new(w, h)).map_err(|e| e.to_string())?;
|
|
|
|
|
|
|
|
|
|
| 222 |
let domain = extract_domain(url);
|
| 223 |
+
let saved_zoom = { let state = app.state::<AppState>(); let tabs = state.tabs.lock().map_err(|_| "lock")?; tabs.get_zoom_for_domain(&domain) };
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 224 |
let now = chrono::Utc::now().timestamp();
|
| 225 |
let zoom = saved_zoom.unwrap_or(1.0);
|
|
|
|
| 226 |
let previous = {
|
| 227 |
let state = app.state::<AppState>();
|
| 228 |
let mut tabs = state.tabs.lock().map_err(|_| "lock")?;
|
| 229 |
let previous = tabs.active.clone();
|
| 230 |
tabs.active = Some(id.clone());
|
| 231 |
tabs.order.push(id.clone());
|
| 232 |
+
tabs.tabs.insert(id.clone(), BrowserTab { id: id.clone(), label: label.clone(), url: url.to_string(), title: "New Tab".to_string(), favicon: None, loading: true, pinned: false, sleeping: false, zoom, can_go_back: false, can_go_forward: false, last_active: now });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 233 |
previous
|
| 234 |
};
|
| 235 |
+
if zoom != 1.0 { if let Some(webview) = app.get_webview(&label) { let _ = webview.set_zoom(zoom); } }
|
| 236 |
+
if let Some(prev) = previous { if prev != id { hide_tab(app, &prev)?; } }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 237 |
emit_snapshot(app)?;
|
| 238 |
Ok(id)
|
| 239 |
}
|
| 240 |
|
| 241 |
+
const FAVICON_AND_NAV_SCRIPT: &str = r#"(function(){window.__muse_report_favicon=function(){const link=document.querySelector('link[rel~="icon"],link[rel="shortcut icon"],link[rel="apple-touch-icon"]');if(link&&link.href){try{window.__TAURI_INTERNALS__.invoke('__tab_favicon',{favicon:link.href});}catch{}}};if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',window.__muse_report_favicon);}else{window.__muse_report_favicon();}})();"#;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|