fix: update browser backend protocol/globals/webview labels to LumaRef
Browse files
src-tauri/src/browser/commands.rs
CHANGED
|
@@ -16,8 +16,8 @@ fn is_duplicate_capture(capture_id: &str) -> bool { if capture_id.is_empty() { r
|
|
| 16 |
|
| 17 |
const CONTEXT_MENU_BLOCK_JS: &str = r#"
|
| 18 |
(function(){
|
| 19 |
-
if(window.
|
| 20 |
-
window.
|
| 21 |
document.addEventListener('contextmenu', function(e) {
|
| 22 |
e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation();
|
| 23 |
try { var t=e.target; window.__TAURI_INTERNALS__.invoke('browser_context_menu',{x:e.screenX,y:e.screenY,clientX:e.clientX,clientY:e.clientY,tagName:t.tagName||null,src:t.src||t.currentSrc||null,href:t.closest&&t.closest('a')?t.closest('a').href:null,text:(window.getSelection()||'').toString().slice(0,200)||null,pageUrl:location.href}); } catch(ex) {}
|
|
@@ -58,24 +58,24 @@ pub fn tab_get_all(app: AppHandle) -> Result<BrowserSnapshot, String> { snapshot
|
|
| 58 |
#[tauri::command]
|
| 59 |
pub fn tab_pin(app: AppHandle, tab_id: String, pinned: bool) -> Result<BrowserSnapshot, String> { update_tab_field(&app,&tab_id,|t|{t.pinned=pinned;}); snapshot(&app) }
|
| 60 |
#[tauri::command]
|
| 61 |
-
pub async fn tab_find(app: AppHandle, tab_id: String, query: String) -> Result<u32, String> { if query.len()>500{return Err("Search query too long".into());} let q=serde_json::to_string(&query).unwrap_or("\"\"".into()); eval_on_tab(&app,&tab_id,&format!(r#"(function(){{window.
|
| 62 |
#[tauri::command]
|
| 63 |
-
pub async fn tab_find_clear(app: AppHandle, tab_id: String) -> Result<(), String> { eval_on_tab(&app,&tab_id,"window.
|
| 64 |
|
| 65 |
pub(crate) async fn create_tab_inner(app:&AppHandle,url:&str,_layout:&ViewportLayout)->Result<String,String>{
|
| 66 |
let id_num=app.state::<AppState>().next_tab_id.fetch_add(1,std::sync::atomic::Ordering::Relaxed);
|
| 67 |
-
let id=format!("tab-{id_num}"); let label=format!("
|
| 68 |
let init_script=scripts::build_init_script(&scripts::blocked_domains_json()); let full_init=format!("{init_script}\n{CONTEXT_MENU_BLOCK_JS}\n{FAVICON_SCRIPT}");
|
| 69 |
let app_for_load=app.clone(); let id_for_load=id.clone(); let app_for_title=app.clone(); let id_for_title=id.clone(); let app_for_cosmetic=app.clone();
|
| 70 |
-
let builder=WebviewBuilder::new(label.clone(),WebviewUrl::External(parsed)).initialization_script(&full_init).on_page_load(move|webview,payload|{let url=payload.url().to_string();let loading=matches!(payload.event(),PageLoadEvent::Started);update_tab_field(&app_for_load,&id_for_load,|t|{if !loading&&t.url!=url&&!t.url.is_empty(){t.can_go_back=true;}t.url=url.clone();t.loading=loading;});if matches!(payload.event(),PageLoadEvent::Finished){let _=webview.eval(CONTEXT_MENU_BLOCK_JS);let _=webview.eval(scripts::hover_overlay_script());let adblock_state=app_for_cosmetic.state::<AdBlockState>();let css=adblock_state.get_cosmetic_css(&url);if !css.is_empty(){let escaped=css.replace('\\',"\\\\").replace('`',"\\`");let _=webview.eval(&format!("(function(){{const s=document.createElement('style');s.id='
|
| 71 |
let window=app.get_window("main").ok_or("main window not found")?; window.add_child(builder,LogicalPosition::new(-32000.0,-32000.0),LogicalSize::new(1.0,1.0)).map_err(|e|e.to_string())?;
|
| 72 |
let domain=extract_domain(url);let saved_zoom={let state=app.state::<AppState>();let tabs=state.tabs.lock().map_err(|_|"lock")?;tabs.get_zoom_for_domain(&domain)};let now=chrono::Utc::now().timestamp();let zoom=saved_zoom.unwrap_or(1.0);
|
| 73 |
let previous={let state=app.state::<AppState>();let mut tabs=state.tabs.lock().map_err(|_|"lock")?;let previous=tabs.active.clone();tabs.active=Some(id.clone());tabs.order.push(id.clone());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});previous};
|
| 74 |
if zoom!=1.0{if let Some(webview)=app.get_webview(&label){let _=webview.set_zoom(zoom);}} if let Some(prev)=previous{if prev!=id{hide_tab(app,&prev)?;}} emit_snapshot(app)?; Ok(id)
|
| 75 |
}
|
| 76 |
|
| 77 |
-
fn
|
| 78 |
-
let rest=raw.trim_start_matches("
|
| 79 |
let params:std::collections::HashMap<String,String>=query.split('&').filter_map(|pair|{let(k,v)=pair.split_once('=')?;Some((percent_decode(k),percent_decode(v)))}).collect();
|
| 80 |
let capture_id=params.get("captureId").cloned().unwrap_or_default(); if is_duplicate_capture(&capture_id){return;}
|
| 81 |
let url=params.get("url").cloned().unwrap_or_default(); if url.is_empty(){return;} if !url.starts_with("http://")&&!url.starts_with("https://"){return;}
|
|
@@ -83,4 +83,4 @@ fn handle_muse_action(app:&AppHandle,raw:&str){
|
|
| 83 |
tauri::async_runtime::spawn(async move{match crate::library::library_add_item(app2.clone(),url.clone(),Some(source.clone()),Some(title.clone())).await{Ok(item)=>{if action=="board"{let _=crate::board::board_add_image(app2.clone(),Some(item.id.clone()),item.data_url.clone(),120.0,120.0,300.0,200.0);}let _=app2.emit("board://image_added",serde_json::json!({"libraryId":item.id,"dataUrl":item.data_url,"url":item.url,"sourceUrl":item.source_url,"title":item.title,"width":item.width,"height":item.height,"colors":item.colors,"captureId":capture_id}));}Err(_)=>{let _=app2.emit("board://image_added",serde_json::json!({"url":url,"dataUrl":url,"sourceUrl":source,"title":title,"width":w,"height":h,"colors":[],"captureId":capture_id}));}}});
|
| 84 |
}
|
| 85 |
fn percent_decode(s:&str)->String{let bytes=s.as_bytes();let mut out=Vec::with_capacity(bytes.len());let mut i=0;while i<bytes.len(){if bytes[i]==b'%'&&i+2<bytes.len(){if let Ok(hex)=std::str::from_utf8(&bytes[i+1..i+3]){if let Ok(v)=u8::from_str_radix(hex,16){out.push(v);i+=3;continue;}}}out.push(if bytes[i]==b'+'{b' '}else{bytes[i]});i+=1;}String::from_utf8_lossy(&out).to_string()}
|
| 86 |
-
const FAVICON_SCRIPT:&str=r#"(function(){window.
|
|
|
|
| 16 |
|
| 17 |
const CONTEXT_MENU_BLOCK_JS: &str = r#"
|
| 18 |
(function(){
|
| 19 |
+
if(window.__lumaref_ctx_installed) return;
|
| 20 |
+
window.__lumaref_ctx_installed = true;
|
| 21 |
document.addEventListener('contextmenu', function(e) {
|
| 22 |
e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation();
|
| 23 |
try { var t=e.target; window.__TAURI_INTERNALS__.invoke('browser_context_menu',{x:e.screenX,y:e.screenY,clientX:e.clientX,clientY:e.clientY,tagName:t.tagName||null,src:t.src||t.currentSrc||null,href:t.closest&&t.closest('a')?t.closest('a').href:null,text:(window.getSelection()||'').toString().slice(0,200)||null,pageUrl:location.href}); } catch(ex) {}
|
|
|
|
| 58 |
#[tauri::command]
|
| 59 |
pub fn tab_pin(app: AppHandle, tab_id: String, pinned: bool) -> Result<BrowserSnapshot, String> { update_tab_field(&app,&tab_id,|t|{t.pinned=pinned;}); snapshot(&app) }
|
| 60 |
#[tauri::command]
|
| 61 |
+
pub async fn tab_find(app: AppHandle, tab_id: String, query: String) -> Result<u32, String> { if query.len()>500{return Err("Search query too long".into());} let q=serde_json::to_string(&query).unwrap_or("\"\"".into()); eval_on_tab(&app,&tab_id,&format!(r#"(function(){{window.__lumaref_find_cleanup&&window.__lumaref_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.__lumaref_find_cleanup=()=>marks.forEach(m=>{{const p=m.parentNode;if(p){{p.replaceChild(document.createTextNode(m.textContent||''),m);p.normalize();}}}});return count;}})()"#))?; Ok(0) }
|
| 62 |
#[tauri::command]
|
| 63 |
+
pub async fn tab_find_clear(app: AppHandle, tab_id: String) -> Result<(), String> { eval_on_tab(&app,&tab_id,"window.__lumaref_find_cleanup && window.__lumaref_find_cleanup()") }
|
| 64 |
|
| 65 |
pub(crate) async fn create_tab_inner(app:&AppHandle,url:&str,_layout:&ViewportLayout)->Result<String,String>{
|
| 66 |
let id_num=app.state::<AppState>().next_tab_id.fetch_add(1,std::sync::atomic::Ordering::Relaxed);
|
| 67 |
+
let id=format!("tab-{id_num}"); let label=format!("lumaref-tab-{id_num}"); let parsed=Url::parse(url).map_err(|e|e.to_string())?;
|
| 68 |
let init_script=scripts::build_init_script(&scripts::blocked_domains_json()); let full_init=format!("{init_script}\n{CONTEXT_MENU_BLOCK_JS}\n{FAVICON_SCRIPT}");
|
| 69 |
let app_for_load=app.clone(); let id_for_load=id.clone(); let app_for_title=app.clone(); let id_for_title=id.clone(); let app_for_cosmetic=app.clone();
|
| 70 |
+
let builder=WebviewBuilder::new(label.clone(),WebviewUrl::External(parsed)).initialization_script(&full_init).on_page_load(move|webview,payload|{let url=payload.url().to_string();let loading=matches!(payload.event(),PageLoadEvent::Started);update_tab_field(&app_for_load,&id_for_load,|t|{if !loading&&t.url!=url&&!t.url.is_empty(){t.can_go_back=true;}t.url=url.clone();t.loading=loading;});if matches!(payload.event(),PageLoadEvent::Finished){let _=webview.eval(CONTEXT_MENU_BLOCK_JS);let _=webview.eval(scripts::hover_overlay_script());let adblock_state=app_for_cosmetic.state::<AdBlockState>();let css=adblock_state.get_cosmetic_css(&url);if !css.is_empty(){let escaped=css.replace('\\',"\\\\").replace('`',"\\`");let _=webview.eval(&format!("(function(){{const s=document.createElement('style');s.id='__lumaref_shield';s.textContent=`{escaped}`;document.head.appendChild(s)}})();"));}let scriptlet_js=adblock_state.get_injected_script(&url);if !scriptlet_js.is_empty(){let _=webview.eval(&format!("try{{{scriptlet_js}}}catch(e){{}}"));}let _=webview.eval("window.__lumaref_report_favicon && window.__lumaref_report_favicon()");let title={let state=app_for_load.state::<AppState>();state.tabs.lock().ok().and_then(|tabs|tabs.tabs.get(&id_for_load).map(|t|t.title.clone())).unwrap_or_default()};let _=crate::history::record_visit(&app_for_load,id_for_load.clone(),url.clone(),title);}}).on_document_title_changed(move|_wv,title|{update_tab_field(&app_for_title,&id_for_title,|t|{if !title.trim().is_empty(){t.title=title.clone();}});}).on_navigation({let app_nav=app.clone();move|url|{let s=url.as_str();if s.starts_with("lumaref-action://"){handle_lumaref_action(&app_nav,s);return false;}if navigation_blocked(&app_nav,s){return false;}true}});
|
| 71 |
let window=app.get_window("main").ok_or("main window not found")?; window.add_child(builder,LogicalPosition::new(-32000.0,-32000.0),LogicalSize::new(1.0,1.0)).map_err(|e|e.to_string())?;
|
| 72 |
let domain=extract_domain(url);let saved_zoom={let state=app.state::<AppState>();let tabs=state.tabs.lock().map_err(|_|"lock")?;tabs.get_zoom_for_domain(&domain)};let now=chrono::Utc::now().timestamp();let zoom=saved_zoom.unwrap_or(1.0);
|
| 73 |
let previous={let state=app.state::<AppState>();let mut tabs=state.tabs.lock().map_err(|_|"lock")?;let previous=tabs.active.clone();tabs.active=Some(id.clone());tabs.order.push(id.clone());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});previous};
|
| 74 |
if zoom!=1.0{if let Some(webview)=app.get_webview(&label){let _=webview.set_zoom(zoom);}} if let Some(prev)=previous{if prev!=id{hide_tab(app,&prev)?;}} emit_snapshot(app)?; Ok(id)
|
| 75 |
}
|
| 76 |
|
| 77 |
+
fn handle_lumaref_action(app:&AppHandle,raw:&str){
|
| 78 |
+
let rest=raw.trim_start_matches("lumaref-action://"); let (action,query)=rest.split_once('?').unwrap_or((rest,""));
|
| 79 |
let params:std::collections::HashMap<String,String>=query.split('&').filter_map(|pair|{let(k,v)=pair.split_once('=')?;Some((percent_decode(k),percent_decode(v)))}).collect();
|
| 80 |
let capture_id=params.get("captureId").cloned().unwrap_or_default(); if is_duplicate_capture(&capture_id){return;}
|
| 81 |
let url=params.get("url").cloned().unwrap_or_default(); if url.is_empty(){return;} if !url.starts_with("http://")&&!url.starts_with("https://"){return;}
|
|
|
|
| 83 |
tauri::async_runtime::spawn(async move{match crate::library::library_add_item(app2.clone(),url.clone(),Some(source.clone()),Some(title.clone())).await{Ok(item)=>{if action=="board"{let _=crate::board::board_add_image(app2.clone(),Some(item.id.clone()),item.data_url.clone(),120.0,120.0,300.0,200.0);}let _=app2.emit("board://image_added",serde_json::json!({"libraryId":item.id,"dataUrl":item.data_url,"url":item.url,"sourceUrl":item.source_url,"title":item.title,"width":item.width,"height":item.height,"colors":item.colors,"captureId":capture_id}));}Err(_)=>{let _=app2.emit("board://image_added",serde_json::json!({"url":url,"dataUrl":url,"sourceUrl":source,"title":title,"width":w,"height":h,"colors":[],"captureId":capture_id}));}}});
|
| 84 |
}
|
| 85 |
fn percent_decode(s:&str)->String{let bytes=s.as_bytes();let mut out=Vec::with_capacity(bytes.len());let mut i=0;while i<bytes.len(){if bytes[i]==b'%'&&i+2<bytes.len(){if let Ok(hex)=std::str::from_utf8(&bytes[i+1..i+3]){if let Ok(v)=u8::from_str_radix(hex,16){out.push(v);i+=3;continue;}}}out.push(if bytes[i]==b'+'{b' '}else{bytes[i]});i+=1;}String::from_utf8_lossy(&out).to_string()}
|
| 86 |
+
const FAVICON_SCRIPT:&str=r#"(function(){window.__lumaref_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.__lumaref_report_favicon);}else{window.__lumaref_report_favicon();}})();"#;
|