1use alloc::boxed::Box;
8use alloc::rc::Rc;
9use alloc::string::{String, ToString};
10use alloc::vec::Vec;
11use base64::Engine;
12use core::cell::RefCell;
13use core::future::poll_fn;
14use core::pin::{Pin, pin};
15use futures_util::FutureExt;
16use std::collections::HashMap;
17use std::sync::Arc;
18
19use http::Response;
20
21use crate::batch::{Runtime, in_runtime};
22use crate::function_registry::FUNCTION_REGISTRY;
23use crate::ipc::{DecodedVariant, IPCMessage, MessageType, decode_data};
24use crate::runtime::{AppEventVariant, IPCSenders, WryBindgenEvent, WryIPC, handle_callbacks};
25
26pub trait ImplWryBindgenResponder {
27 fn respond(self: Box<Self>, response: Response<Vec<u8>>);
28}
29
30pub struct WryBindgenResponder {
32 respond: Box<dyn ImplWryBindgenResponder>,
33}
34
35impl<F> From<F> for WryBindgenResponder
36where
37 F: FnOnce(Response<Vec<u8>>) + 'static,
38{
39 fn from(respond: F) -> Self {
40 struct FnOnceWrapper<F> {
41 f: F,
42 }
43
44 impl<F> ImplWryBindgenResponder for FnOnceWrapper<F>
45 where
46 F: FnOnce(Response<Vec<u8>>) + 'static,
47 {
48 fn respond(self: Box<Self>, response: Response<Vec<u8>>) {
49 (self.f)(response)
50 }
51 }
52
53 Self {
54 respond: Box::new(FnOnceWrapper { f: respond }),
55 }
56 }
57}
58
59impl WryBindgenResponder {
60 pub fn new(f: impl ImplWryBindgenResponder + 'static) -> Self {
61 Self {
62 respond: Box::new(f),
63 }
64 }
65
66 fn respond(self, response: Response<Vec<u8>>) {
67 self.respond.respond(response);
68 }
69}
70
71fn decode_request_data(request: &http::Request<Vec<u8>>) -> Option<IPCMessage> {
73 if let Some(header_value) = request.headers().get("dioxus-data") {
74 return decode_data(header_value.as_bytes());
75 }
76 None
77}
78
79enum WebviewLoadingState {
81 Pending { queued: Vec<IPCMessage> },
83 Loaded,
85}
86
87impl Default for WebviewLoadingState {
88 fn default() -> Self {
89 WebviewLoadingState::Pending { queued: Vec::new() }
90 }
91}
92
93struct WebviewState {
95 ongoing_request: Option<WryBindgenResponder>,
96 pending_js_evaluates: usize,
98 pending_rust_evaluates: usize,
100 sender: IPCSenders,
102 loading_state: WebviewLoadingState,
104 evaluate_script: Box<dyn FnMut(&str)>,
106}
107
108impl WebviewState {
109 fn new(sender: IPCSenders, evaluate_script: impl FnMut(&str) + 'static) -> Self {
111 Self {
112 ongoing_request: None,
113 pending_js_evaluates: 0,
114 pending_rust_evaluates: 0,
115 sender,
116 loading_state: WebviewLoadingState::default(),
117 evaluate_script: Box::new(evaluate_script),
118 }
119 }
120
121 fn set_ongoing_request(&mut self, responder: WryBindgenResponder) {
122 if self.ongoing_request.is_some() {
123 panic!(
124 "WARNING: Overwriting existing ongoing_request! Previous request will never be responded to."
125 );
126 }
127 self.ongoing_request = Some(responder);
128 }
129
130 fn take_ongoing_request(&mut self) -> Option<WryBindgenResponder> {
131 self.ongoing_request.take()
132 }
133
134 fn has_pending_request(&self) -> bool {
135 self.ongoing_request.is_some()
136 }
137
138 fn respond_to_request(&mut self, response: IPCMessage) {
139 if let Some(responder) = self.take_ongoing_request() {
140 let body = response.into_data();
141 let engine = base64::engine::general_purpose::STANDARD;
143 let body_base64 = engine.encode(&body);
144 responder.respond(
145 http::Response::builder()
146 .status(200)
147 .header("Content-Type", "text/plain")
148 .body(body_base64.into_bytes())
149 .expect("Failed to build response"),
150 );
151 } else {
152 panic!("WARNING: respond_to_request called but no pending request! Response dropped.");
153 }
154 }
155
156 fn evaluate_script(&mut self, script: &str) {
157 (self.evaluate_script)(script);
158 }
159}
160
161fn unique_id() -> u64 {
162 use core::sync::atomic::{AtomicU64, Ordering};
163 static COUNTER: AtomicU64 = AtomicU64::new(0);
164
165 COUNTER.fetch_add(1, Ordering::Relaxed)
166}
167
168pub struct PreparedApp {
173 id: u64,
174 future: Box<dyn FnOnce() -> Pin<Box<dyn core::future::Future<Output = ()> + 'static>> + Send>,
175}
176
177impl PreparedApp {
178 pub fn id(&self) -> u64 {
180 self.id
181 }
182
183 pub fn into_future(self) -> Pin<Box<dyn core::future::Future<Output = ()> + 'static>> {
185 (self.future)()
186 }
187}
188
189pub struct ProtocolHandler {
194 id: u64,
195 webview: Rc<RefCell<HashMap<u64, WebviewState>>>,
196}
197
198impl ProtocolHandler {
199 pub fn handle_request<F, R: Into<WryBindgenResponder>>(
211 &self,
212 protocol: &str,
213 proxy: F,
214 request: &http::Request<Vec<u8>>,
215 responder: R,
216 ) -> Option<R>
217 where
218 F: Fn(WryBindgenEvent),
219 {
220 let webviews = &self.webview;
221 let webview_id = self.id;
222
223 let protocol_prefix = format!("{protocol}://index.html");
224 let android_prefix = format!("https://{protocol}.index.html");
225 let windows_prefix = format!("http://{protocol}.index.html");
226
227 let uri = request.uri().to_string();
228 let real_path = uri
229 .strip_prefix(&protocol_prefix)
230 .or_else(|| uri.strip_prefix(&windows_prefix))
231 .or_else(|| uri.strip_prefix(&android_prefix))
232 .unwrap_or(&uri);
233 let real_path = real_path.trim_matches('/');
234
235 let Some(path_without_wbg) = real_path.strip_prefix("__wbg__/") else {
236 return Some(responder);
238 };
239
240 if let Some(path_without_snippets) = path_without_wbg.strip_prefix("snippets/") {
242 let responder = responder.into();
243 if let Some(content) = FUNCTION_REGISTRY.get_module(path_without_snippets) {
244 responder.respond(module_response(content));
245 return None;
246 }
247 responder.respond(not_found_response());
248 return None;
249 }
250
251 if path_without_wbg == "init.js" {
252 let responder = responder.into();
253 responder.respond(module_response(&init_script()));
254 return None;
255 }
256
257 if path_without_wbg == "initialized" {
258 proxy(WryBindgenEvent::webview_loaded(webview_id));
259 let responder = responder.into();
260 responder.respond(blank_response());
261 return None;
262 }
263
264 if path_without_wbg == "handler" {
266 let responder = responder.into();
267 let mut webviews = webviews.borrow_mut();
268 let Some(webview_state) = webviews.get_mut(&webview_id) else {
269 responder.respond(error_response());
270 return None;
271 };
272 let Some(msg) = decode_request_data(request) else {
273 responder.respond(error_response());
274 return None;
275 };
276 let msg_type = msg.ty().unwrap();
277 match msg_type {
278 MessageType::Evaluate => {
280 webview_state.pending_rust_evaluates += 1;
281 webview_state.set_ongoing_request(responder);
282 }
283 MessageType::Respond => {
285 webview_state.pending_js_evaluates =
286 webview_state.pending_js_evaluates.saturating_sub(1);
287 if webview_state.pending_rust_evaluates > 0
288 || webview_state.pending_js_evaluates > 0
289 {
290 webview_state.set_ongoing_request(responder);
292 } else {
293 responder.respond(blank_response());
295 }
296 }
297 }
298 webview_state.sender.start_send(msg);
299 return None;
300 }
301
302 Some(responder)
303 }
304}
305
306fn init_script() -> String {
310 const INITIALIZATION_SCRIPT: &str = include_str!("./js/main.js");
312 let collect_functions = FUNCTION_REGISTRY.script();
313 format!("{INITIALIZATION_SCRIPT}\n{collect_functions}")
314}
315
316pub struct WryBindgen {
343 event_loop_proxy: Arc<dyn Fn(WryBindgenEvent) + Send + Sync>,
344 webview: Rc<RefCell<HashMap<u64, WebviewState>>>,
346}
347
348impl WryBindgen {
349 pub fn new(event_loop_proxy: impl Fn(WryBindgenEvent) + Send + Sync + 'static) -> Self {
351 Self {
352 event_loop_proxy: Arc::new(event_loop_proxy),
353 webview: Rc::new(RefCell::new(HashMap::new())),
354 }
355 }
356
357 pub fn app_builder<'a>(&'a self) -> AppBuilder<'a> {
363 let event_loop_proxy = self.event_loop_proxy.clone();
364 let webview_id = unique_id();
365 let (ipc, senders) = WryIPC::new(event_loop_proxy);
366 self.webview.borrow_mut().insert(
367 webview_id,
368 WebviewState::new(senders, |_| {
369 unreachable!("evaluate_script will only be used after spawning the app")
370 }),
371 );
372
373 AppBuilder {
374 webview_id,
375 bindgen: self,
376 ipc,
377 }
378 }
379
380 pub fn handle_user_event(&self, event: WryBindgenEvent) {
389 let id = event.id();
390 match event.into_variant() {
391 AppEventVariant::Ipc(ipc_msg) => self.handle_ipc_message(id, ipc_msg),
393 AppEventVariant::WebviewLoaded => {
394 let mut state = self.webview.borrow_mut();
395 let Some(webview_state) = state.get_mut(&id) else {
396 return;
397 };
398 if let WebviewLoadingState::Pending { queued } = std::mem::replace(
399 &mut webview_state.loading_state,
400 WebviewLoadingState::Loaded,
401 ) {
402 for msg in queued {
403 self.immediately_handle_ipc_message(webview_state, msg);
404 }
405 }
406 }
407 }
408 }
409
410 fn handle_ipc_message(&self, id: u64, ipc_msg: IPCMessage) {
411 let mut state = self.webview.borrow_mut();
412 let Some(webview_state) = state.get_mut(&id) else {
413 return;
414 };
415 if let WebviewLoadingState::Pending { queued } = &mut webview_state.loading_state {
416 queued.push(ipc_msg);
417 return;
418 }
419
420 self.immediately_handle_ipc_message(webview_state, ipc_msg)
421 }
422
423 fn immediately_handle_ipc_message(
424 &self,
425 webview_state: &mut WebviewState,
426 ipc_msg: IPCMessage,
427 ) {
428 let ty = ipc_msg.ty().unwrap();
429 match ty {
430 MessageType::Evaluate => {
432 webview_state.pending_js_evaluates += 1;
433 }
434 MessageType::Respond => {
436 webview_state.pending_rust_evaluates =
437 webview_state.pending_rust_evaluates.saturating_sub(1);
438 }
439 }
440
441 if webview_state.has_pending_request() {
443 webview_state.respond_to_request(ipc_msg);
444 return;
445 }
446
447 let decoded = ipc_msg.decoded().unwrap();
449
450 if let DecodedVariant::Evaluate { .. } = decoded {
451 let engine = base64::engine::general_purpose::STANDARD;
454 let data_base64 = engine.encode(ipc_msg.data());
455 let code = format!("window.evaluate_from_rust_binary(\"{data_base64}\")");
456 webview_state.evaluate_script(&code);
457 }
458 }
459}
460
461pub struct AppBuilder<'a> {
463 webview_id: u64,
464 bindgen: &'a WryBindgen,
465 ipc: WryIPC,
466}
467
468impl<'a> AppBuilder<'a> {
469 pub fn protocol_handler(&self) -> ProtocolHandler {
471 ProtocolHandler {
472 id: self.webview_id,
473 webview: self.bindgen.webview.clone(),
474 }
475 }
476
477 pub fn build<F>(
479 self,
480 app: impl FnOnce() -> F + Send + 'static,
481 evaluate_script: impl FnMut(&str) + 'static,
482 ) -> PreparedApp
483 where
484 F: core::future::Future<Output = ()> + 'static,
485 {
486 {
488 let mut webviews = self.bindgen.webview.borrow_mut();
489 let webview_state = webviews
490 .get_mut(&self.webview_id)
491 .expect("The webview state was created in WryBindgen::spawner");
492 webview_state.evaluate_script = Box::new(evaluate_script);
493 }
494
495 let start_future = move || {
496 let run_app_in_runtime = async move {
497 let run_app = app();
498 let wait_for_events = handle_callbacks();
499
500 futures_util::select! {
501 _ = run_app.fuse() => {},
502 _ = wait_for_events.fuse() => {},
503 }
504 };
505
506 let runtime = Runtime::new(self.ipc, self.webview_id);
507 let mut maybe_runtime = Some(runtime);
508 let poll_in_runtime = async move {
509 let mut run_app_in_runtime = pin!(run_app_in_runtime);
510 poll_fn(move |ctx| {
511 let (new_runtime, poll_result) =
512 in_runtime(maybe_runtime.take().unwrap(), || {
513 run_app_in_runtime.as_mut().poll(ctx)
514 });
515 maybe_runtime = Some(new_runtime);
516 poll_result
517 })
518 .await
519 };
520
521 Box::pin(poll_in_runtime) as Pin<Box<dyn Future<Output = ()> + 'static>>
522 };
523
524 PreparedApp {
525 id: self.webview_id,
526 future: Box::new(start_future),
527 }
528 }
529}
530
531pub fn blank_response() -> http::Response<Vec<u8>> {
533 http::Response::builder()
534 .status(200)
535 .body(vec![])
536 .expect("Failed to build blank response")
537}
538
539pub fn error_response() -> http::Response<Vec<u8>> {
541 http::Response::builder()
542 .status(400)
543 .body(vec![])
544 .expect("Failed to build error response")
545}
546
547pub fn module_response(content: &str) -> http::Response<Vec<u8>> {
549 http::Response::builder()
550 .status(200)
551 .header("Content-Type", "application/javascript")
552 .header("access-control-allow-origin", "*")
553 .body(content.as_bytes().to_vec())
554 .expect("Failed to build module response")
555}
556
557pub fn not_found_response() -> http::Response<Vec<u8>> {
559 http::Response::builder()
560 .status(404)
561 .body(b"Not Found".to_vec())
562 .expect("Failed to build not found response")
563}