1use std::{
5 collections::HashMap,
6 io::{self, BufRead, BufReader, Read, Write},
7 net::TcpListener,
8 sync::{Arc, Mutex},
9 time::Duration,
10};
11
12use once_cell::sync::Lazy;
13use serde_json::{Value, json};
14use tauri::{WebviewWindow, Wry};
15use url::Url;
16
17static WINDOW:Lazy<Mutex<Option<Arc<WebviewWindow<Wry>>>>> = Lazy::new(|| Mutex::new(None));
19
20pub fn install(window:&WebviewWindow<Wry>) {
23 let mut guard = WINDOW.lock().unwrap();
25 *guard = Some(Arc::new(window.clone()));
26
27 let enable = std::env::var("DebugServer").map(|v| !v.is_empty() && v != "0").unwrap_or(false);
29 if enable {
30 std::thread::spawn(|| {
31 start_server();
32 });
33 }
34}
35
36fn start_server() {
38 let port = std::env::var("DebugServerPort")
39 .ok()
40 .and_then(|p| p.parse().ok())
41 .unwrap_or(9933);
42
43 let listener = match TcpListener::bind(("127.0.0.1", port)) {
44 Ok(l) => l,
45 Err(e) => {
46 eprintln!("[WebkitDebug] Failed to bind to 127.0.0.1:{}: {}", port, e);
47 return;
48 },
49 };
50 eprintln!("[WebkitDebug] Server listening on http://127.0.0.1:{}", port);
51
52 for stream in listener.incoming() {
53 match stream {
54 Ok(mut stream) => {
55 let window_opt = WINDOW.lock().unwrap().clone();
56 std::thread::spawn(move || {
57 if let Err(e) = handle_connection(&window_opt, &mut stream) {
58 eprintln!("[WebkitDebug] Connection error: {}", e);
59 }
60 });
61 },
62 Err(e) => eprintln!("[WebkitDebug] Accept error: {}", e),
63 }
64 }
65}
66
67fn handle_connection(window_opt:&Option<Arc<WebviewWindow<Wry>>>, stream:&mut std::net::TcpStream) -> io::Result<()> {
69 if window_opt.is_none() {
71 send_json(stream, 503, &json!({"error": "debug server not initialized"}))?;
72 return Ok(());
73 }
74
75 let (method, path_and_query, body) = {
77 let mut reader = BufReader::new(&mut *stream);
78 let mut request_line = String::new();
79 reader.read_line(&mut request_line)?;
80 let request_line = request_line.trim_end();
81 let parts:Vec<&str> = request_line.split_whitespace().collect();
82 if parts.len() != 3 {
83 return Err(io::Error::new(io::ErrorKind::InvalidInput, "bad request line"));
85 }
86 let method = parts[0].to_string();
87 let path_and_query = parts[1].to_string();
88
89 let mut headers = HashMap::new();
91 loop {
92 let mut line = String::new();
93 let n = reader.read_line(&mut line)?;
94 if n == 0 || line == "\r\n" {
95 break;
96 }
97 if let Some(idx) = line.find(':') {
98 let name = line[..idx].trim().to_uppercase();
99 let value = line[idx + 1..].trim().to_string();
100 headers.insert(name, value);
101 }
102 }
103
104 let body = if let Some(len_str) = headers.get("CONTENT-LENGTH") {
106 let len:usize = len_str.parse().unwrap_or(0);
107 let mut body_bytes = vec![0; len];
108 reader.read_exact(&mut body_bytes)?;
109 String::from_utf8_lossy(&body_bytes).to_string()
110 } else {
111 String::new()
112 };
113
114 (method, path_and_query, body)
115 };
116
117 let full_url = format!("http://localhost{}", path_and_query);
119 let parsed = Url::parse(&full_url).map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid URL"))?;
120 let path = parsed.path();
121 let mut query_pairs = parsed.query_pairs();
122
123 let (status, response_json) = match method.as_str() {
125 "GET" if path == "/console" => {
126 let js = r#"(function() {
127 const logs = window.__MOUNTAIN_DEBUG_CONSOLE || [];
128 window.__MOUNTAIN_DEBUG_CONSOLE = [];
129 return JSON.stringify(logs);
130 })()"#;
131 match eval_js(window_opt, js) {
132 Ok(value) => (200, json!({"logs": value})),
133 Err(e) => (500, json!({"error": e})),
134 }
135 },
136 "GET" if path == "/eval" => {
137 let js = query_pairs
138 .find(|(k, _)| k == "js")
139 .map(|(_, v)| v.into_owned())
140 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "missing js parameter"))?;
141 match eval_js(window_opt, &js) {
142 Ok(value) => (200, json!({"result": value})),
143 Err(e) => (500, json!({"error": e})),
144 }
145 },
146 "POST" if path == "/execute" => {
147 let parsed_body:Value =
148 serde_json::from_str(&body).map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
149 let js = parsed_body["js"]
150 .as_str()
151 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "missing js field"))?;
152 if js.is_empty() {
153 (400, json!({"error": "empty js"}))
154 } else {
155 match eval_js(window_opt, js) {
156 Ok(val) => (200, json!({"result": val})),
157 Err(e) => (500, json!({"error": e})),
158 }
159 }
160 },
161 "GET" if path == "/iframes" => {
162 let js = r#"(function() {
163 const frames = document.querySelectorAll('iframe');
164 const arr = [];
165 frames.forEach(f => {
166 arr.push({
167 src: f.src,
168 id: f.id,
169 name: f.name,
170 contentWindow: !!f.contentWindow
171 });
172 });
173 return JSON.stringify(arr);
174 })()"#;
175 match eval_js(window_opt, js) {
176 Ok(value) => (200, json!({"iframes": value})),
177 Err(e) => (500, json!({"error": e})),
178 }
179 },
180 _ => (404, json!({"error": "not found"})),
181 };
182
183 send_json(stream, status, &response_json)
184}
185
186fn eval_js(window_opt:&Option<Arc<WebviewWindow<Wry>>>, js:&str) -> Result<Value, String> {
189 let window = window_opt.as_ref().ok_or("debug server not initialized")?;
190 let (tx, rx) = std::sync::mpsc::sync_channel(1);
191 window
192 .eval_with_callback(js.to_string(), move |result| {
193 let _ = tx.send(result);
194 })
195 .map_err(|e| e.to_string())?;
196 let result_str = rx
197 .recv_timeout(Duration::from_secs(5))
198 .map_err(|_| "timeout waiting for eval result".to_string())?;
199 serde_json::from_str(&result_str).map_err(|e| e.to_string())
200}
201
202fn send_json(stream:&mut std::net::TcpStream, status:u16, value:&Value) -> io::Result<()> {
204 let body =
205 serde_json::to_string(value).map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "serialization error"))?;
206 let status_text = match status {
207 200 => "OK",
208 400 => "Bad Request",
209 404 => "Not Found",
210 500 => "Internal Server Error",
211 503 => "Service Unavailable",
212 _ => "OK",
213 };
214 let headers = format!(
215 "HTTP/1.1 {} {}\r\nContent-Type: application/json\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
216 status,
217 status_text,
218 body.len()
219 );
220 stream.write_all(headers.as_bytes())?;
221 stream.write_all(body.as_bytes())?;
222 stream.flush()?;
223 Ok(())
224}