asdf98 commited on
Commit
c0f31a5
·
verified ·
1 Parent(s): 753ec4e

Fix tab_zoom borrow and remove unused Ordering import

Browse files
Files changed (1) hide show
  1. 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
- // Push to closed stack for restore
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; // After navigating, back is possible
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 (label, domain) = {
156
  let state = app.state::<AppState>();
157
  let mut tabs = state.tabs.lock().map_err(|_| "lock")?;
158
- let tab = tabs.tabs.get_mut(&tab_id).ok_or("tab not found")?;
159
- tab.zoom = zoom;
160
- let domain = extract_domain(&tab.url);
 
 
 
161
  tabs.remember_zoom(&domain, zoom);
162
- (tab.label.clone(), domain)
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) // Count returned via JS, not directly capturable without eval callback
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
- let app_t = app_for_title;
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
- // Apply saved zoom
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
- /// JS injected into every tab for favicon extraction and nav state reporting
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();}})();"#;