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;
15use core::task::Poll;
16
17use http::Response;
18
19use crate::batch::{Runtime, in_runtime};
20use crate::function_registry::FUNCTION_REGISTRY;
21use crate::ipc::{IPCMessage, decode_data};
22use crate::runtime::{
23 DriverCommand, DriverCommandReceiver, DriverCommandSender, DriverCommandWeakSender, IPCSenders,
24 Inbound, InboundSendError, WryIPC, dispatch_inbound_message,
25};
26
27struct WryBindgenResponder {
28 respond: Box<dyn FnOnce(Response<Vec<u8>>)>,
29}
30
31impl<F> From<F> for WryBindgenResponder
32where
33 F: FnOnce(Response<Vec<u8>>) + 'static,
34{
35 fn from(respond: F) -> Self {
36 Self {
37 respond: Box::new(respond),
38 }
39 }
40}
41
42impl WryBindgenResponder {
43 fn respond(self, response: Response<Vec<u8>>) {
44 (self.respond)(response);
45 }
46
47 fn respond_ipc(self, response: IPCMessage) {
48 let body = response.data();
49 let engine = base64::engine::general_purpose::STANDARD;
51 let body_base64 = engine.encode(body);
52 self.respond(
53 http::Response::builder()
54 .status(200)
55 .header("Content-Type", "text/plain")
56 .body(body_base64.into_bytes())
57 .expect("Failed to build response"),
58 );
59 }
60}
61
62fn decode_request_data(request: &http::Request<Vec<u8>>) -> Option<IPCMessage> {
64 if let Some(header_value) = request.headers().get("dioxus-data") {
65 return decode_data(header_value.as_bytes());
66 }
67 None
68}
69
70enum WebviewLoadingState {
72 Pending {
76 pending_ipc: Option<IPCMessage>,
77 acquire_lock: bool,
78 },
79 Loaded,
81}
82
83impl Default for WebviewLoadingState {
84 fn default() -> Self {
85 WebviewLoadingState::Pending {
86 pending_ipc: None,
87 acquire_lock: false,
88 }
89 }
90}
91
92struct WebviewState {
94 messages: WebviewMessageLayer,
96 loading_state: WebviewLoadingState,
98}
99
100struct WebviewMessageLayer {
110 current_xhr: Option<WryBindgenResponder>,
111 sender: IPCSenders,
113}
114
115impl WebviewState {
116 fn new(sender: IPCSenders) -> Self {
118 Self {
119 messages: WebviewMessageLayer::new(sender),
120 loading_state: WebviewLoadingState::default(),
121 }
122 }
123
124 fn handle_driver_command(&mut self, command: DriverCommand) -> DriverAction {
125 match command {
126 DriverCommand::AcquireLock => self.handle_acquire_lock(),
127 DriverCommand::SendIpc(ipc_msg) => {
128 self.handle_ipc_message(ipc_msg);
129 DriverAction::None
130 }
131 DriverCommand::ReleaseLock => {
132 self.messages.release_lock();
133 DriverAction::None
134 }
135 }
136 }
137
138 fn handle_ipc_message(&mut self, ipc_msg: IPCMessage) {
139 if let WebviewLoadingState::Pending { pending_ipc, .. } = &mut self.loading_state {
140 assert!(
141 pending_ipc.replace(ipc_msg).is_none(),
142 "multiple Rust IPC messages queued before webview load"
143 );
144 return;
145 }
146
147 self.messages.receive_rust_message(ipc_msg);
148 }
149
150 fn handle_acquire_lock(&mut self) -> DriverAction {
151 if let WebviewLoadingState::Pending { acquire_lock, .. } = &mut self.loading_state {
152 *acquire_lock = true;
153 return DriverAction::None;
154 }
155
156 DriverAction::RequestJsLock
157 }
158
159 fn mark_loaded(&mut self) -> bool {
160 if let WebviewLoadingState::Pending {
161 pending_ipc,
162 acquire_lock,
163 } = std::mem::replace(&mut self.loading_state, WebviewLoadingState::Loaded)
164 {
165 if let Some(msg) = pending_ipc {
166 self.messages.receive_rust_message(msg);
167 }
168 return acquire_lock;
169 }
170
171 false
172 }
173
174 fn mark_initialized(&mut self) -> bool {
175 match self.loading_state {
176 WebviewLoadingState::Pending { .. } => self.mark_loaded(),
177 WebviewLoadingState::Loaded => true,
178 }
179 }
180}
181
182enum DriverAction {
183 None,
184 RequestJsLock,
185}
186
187impl DriverAction {
188 fn run(self, evaluate_script: &mut impl FnMut(&str)) {
189 match self {
190 DriverAction::None => {}
191 DriverAction::RequestJsLock => {
192 evaluate_script("window.__wry_acquire_handler_lock()");
193 }
194 }
195 }
196}
197
198impl WebviewMessageLayer {
199 fn new(sender: IPCSenders) -> Self {
200 Self {
201 current_xhr: None,
202 sender,
203 }
204 }
205
206 fn receive_js_message(&mut self, msg: IPCMessage, responder: WryBindgenResponder) {
207 self.park_and_forward(responder, Inbound::Message(msg));
208 }
209
210 fn receive_lock_request(&mut self, responder: WryBindgenResponder) {
211 self.park_and_forward(responder, Inbound::LockReady);
212 }
213
214 fn park_and_forward(&mut self, responder: WryBindgenResponder, inbound: Inbound) {
215 assert!(
216 self.current_xhr.is_none(),
217 "JS parked a new XHR while another JS XHR is waiting for Rust"
218 );
219 self.current_xhr = Some(responder);
220 match self.sender.send(inbound) {
221 Ok(()) => {}
222 Err(InboundSendError::Closed) => {
223 let responder = self.take_parked_xhr();
224 responder.respond(error_response());
225 }
226 Err(InboundSendError::Occupied) => {
227 panic!("inbound IPC slot occupied while parking a JS XHR")
228 }
229 }
230 }
231
232 fn receive_rust_message(&mut self, ipc_msg: IPCMessage) {
233 let responder = self.take_parked_xhr();
237 responder.respond_ipc(ipc_msg);
238 }
239
240 fn release_lock(&mut self) {
241 let responder = self.take_parked_xhr();
242 responder.respond(blank_response());
243 }
244
245 fn take_parked_xhr(&mut self) -> WryBindgenResponder {
248 self.current_xhr.take().unwrap()
249 }
250}
251
252pub struct ProtocolHandler {
256 webview: Rc<RefCell<WebviewState>>,
257 driver_commands: DriverCommandWeakSender,
258}
259
260impl ProtocolHandler {
261 pub fn handle_request<R>(
273 &self,
274 protocol: &str,
275 request: &http::Request<Vec<u8>>,
276 responder: R,
277 ) -> Option<R>
278 where
279 R: FnOnce(Response<Vec<u8>>) + 'static,
280 {
281 let webviews = &self.webview;
282
283 let protocol_prefix = format!("{protocol}://index.html");
284 let android_prefix = format!("https://{protocol}.index.html");
285 let windows_prefix = format!("http://{protocol}.index.html");
286
287 let uri = request.uri().to_string();
288 let real_path = uri
289 .strip_prefix(&protocol_prefix)
290 .or_else(|| uri.strip_prefix(&windows_prefix))
291 .or_else(|| uri.strip_prefix(&android_prefix))
292 .unwrap_or(&uri);
293 let real_path = real_path.trim_matches('/');
294
295 let Some(path_without_wbg) = real_path.strip_prefix("__wbg__/") else {
296 return Some(responder);
298 };
299
300 if let Some(path_without_snippets) = path_without_wbg.strip_prefix("snippets/") {
302 let responder = WryBindgenResponder::from(responder);
303 if let Some(content) = FUNCTION_REGISTRY
306 .get_module(path_without_snippets)
307 .or_else(|| crate::function_registry::linked_module(path_without_snippets))
308 {
309 responder.respond(module_response(content));
310 return None;
311 }
312 responder.respond(not_found_response());
313 return None;
314 }
315
316 if path_without_wbg == "init.js" {
317 let responder = WryBindgenResponder::from(responder);
318 responder.respond(module_response(&init_script()));
319 return None;
320 }
321
322 if path_without_wbg == "preinitialized" {
323 let _ = webviews.borrow_mut().mark_loaded();
324 let responder = WryBindgenResponder::from(responder);
325 responder.respond(blank_response());
326 return None;
327 }
328
329 if path_without_wbg == "initialized" {
330 let acquire_lock = webviews.borrow_mut().mark_initialized();
331 if acquire_lock {
332 self.driver_commands.send(DriverCommand::AcquireLock);
333 }
334 let responder = WryBindgenResponder::from(responder);
335 responder.respond(blank_response());
336 return None;
337 }
338
339 if path_without_wbg == "handler" {
341 let responder = WryBindgenResponder::from(responder);
342 let mut webview_state = webviews.borrow_mut();
343 if request.headers().get("wry-bindgen-lock").is_some() {
344 webview_state.messages.receive_lock_request(responder);
345 return None;
346 }
347 let Some(msg) = decode_request_data(request) else {
348 responder.respond(error_response());
349 return None;
350 };
351 webview_state.messages.receive_js_message(msg, responder);
352 return None;
353 }
354
355 Some(responder)
356 }
357}
358
359fn init_script() -> String {
363 const INITIALIZATION_SCRIPT: &str = include_str!("./js/main.js");
365 let collect_functions = FUNCTION_REGISTRY.script();
366 format!("{INITIALIZATION_SCRIPT}\n{collect_functions}")
367}
368
369pub struct WryBindgen {
374 webview: Rc<RefCell<WebviewState>>,
375 ipc: WryIPC,
376 driver_commands: DriverCommandReceiver,
377 weak_driver_commands: DriverCommandWeakSender,
378}
379
380impl WryBindgen {
381 pub fn new() -> Self {
383 let (ipc, senders, driver_commands) = WryIPC::new();
384 let weak_driver_commands = ipc.command_sender().downgrade();
385 Self {
386 webview: Rc::new(RefCell::new(WebviewState::new(senders))),
387 ipc,
388 driver_commands,
389 weak_driver_commands,
390 }
391 }
392
393 pub fn protocol_handler(&self) -> ProtocolHandler {
395 ProtocolHandler {
396 webview: self.webview.clone(),
397 driver_commands: self.weak_driver_commands.clone(),
398 }
399 }
400
401 pub fn split(self) -> (WryBindgenRuntime, WryBindgenDriver) {
403 (
404 WryBindgenRuntime { ipc: self.ipc },
405 WryBindgenDriver {
406 webview: self.webview,
407 commands: self.driver_commands,
408 },
409 )
410 }
411}
412
413impl Default for WryBindgen {
414 fn default() -> Self {
415 Self::new()
416 }
417}
418
419struct JsLockGuard {
426 commands: DriverCommandSender,
427}
428
429impl JsLockGuard {
430 fn acquire(ipc: &WryIPC) -> Self {
431 Self {
432 commands: ipc.command_sender(),
433 }
434 }
435}
436
437impl Drop for JsLockGuard {
438 fn drop(&mut self) {
439 self.commands.send(DriverCommand::ReleaseLock);
440 }
441}
442
443pub struct WryBindgenRuntime {
445 ipc: WryIPC,
446}
447
448impl WryBindgenRuntime {
449 pub fn run<F, Fut>(
452 self,
453 app: F,
454 ) -> impl IntoFuture<Output = (), IntoFuture: 'static> + Send + 'static
455 where
456 F: FnOnce() -> Fut + Send + 'static,
457 Fut: core::future::Future<Output = ()> + 'static,
458 {
459 struct RuntimeFuture<F, Fut> {
460 app: F,
461 ipc: WryIPC,
462 phantom: core::marker::PhantomData<fn(Fut)>,
463 }
464
465 impl<F, Fut> RuntimeFuture<F, Fut> {
466 fn new(app: F, ipc: WryIPC) -> Self {
467 Self {
468 app,
469 ipc,
470 phantom: core::marker::PhantomData,
471 }
472 }
473 }
474
475 impl<F, Fut> IntoFuture for RuntimeFuture<F, Fut>
476 where
477 F: FnOnce() -> Fut + Send + 'static,
478 Fut: core::future::Future<Output = ()> + 'static,
479 {
480 type IntoFuture = Pin<Box<dyn core::future::Future<Output = ()>>>;
481 type Output = ();
482
483 fn into_future(self) -> Self::IntoFuture {
484 let Self { app, ipc, .. } = self;
485 let mut runtime = Some(Runtime::new(ipc));
486 let mut app = Some(app);
487 let mut run_app = None::<Pin<Box<Fut>>>;
488
489 let poll_driver = poll_fn(move |ctx| {
495 let mut just_polled_app = false;
496 loop {
497 let Some(rt) = runtime.as_ref() else {
498 return Poll::Ready(());
499 };
500 match rt.ipc().poll_recv(ctx) {
501 Poll::Ready(Some(Inbound::Message(msg))) => {
505 let owned = runtime.take().expect("runtime available");
506 let (owned, _) =
507 in_runtime(owned, || dispatch_inbound_message(&msg));
508 runtime = Some(owned);
509 just_polled_app = false;
510 }
511 Poll::Ready(Some(Inbound::LockReady)) => {
514 let _guard = JsLockGuard::acquire(rt.ipc());
515 if run_app.is_none() {
516 run_app = Some(Box::pin(app
517 .take()
518 .expect("app constructor called once")(
519 )));
520 }
521 let owned = runtime.take().expect("runtime available");
522 let (owned, poll_result) = in_runtime(owned, || {
523 run_app
524 .as_mut()
525 .expect("app future must exist")
526 .as_mut()
527 .poll(ctx)
528 });
529 runtime = Some(owned);
530 if poll_result.is_ready() {
531 return Poll::Ready(());
532 }
533 just_polled_app = true;
534 }
535 Poll::Ready(None) => return Poll::Ready(()),
536 Poll::Pending => {
537 if !just_polled_app {
541 rt.ipc().send_acquire_lock();
542 }
543 return Poll::Pending;
544 }
545 }
546 }
547 });
548
549 Box::pin(poll_driver)
550 }
551 }
552
553 RuntimeFuture::new(app, self.ipc)
554 }
555}
556
557pub struct WryBindgenDriver {
559 webview: Rc<RefCell<WebviewState>>,
560 commands: DriverCommandReceiver,
561}
562
563impl WryBindgenDriver {
564 pub fn with_evaluate_script(
569 self,
570 evaluate_script: impl FnMut(&str) + 'static,
571 ) -> WryBindgenWebviewDriver {
572 WryBindgenWebviewDriver {
573 driver: self,
574 evaluate_script: Box::new(evaluate_script),
575 }
576 }
577}
578
579pub struct WryBindgenWebviewDriver {
581 driver: WryBindgenDriver,
582 evaluate_script: Box<dyn FnMut(&str)>,
583}
584
585impl WryBindgenWebviewDriver {
586 pub fn poll(&mut self, cx: &mut core::task::Context<'_>) -> Poll<()> {
589 loop {
590 match self.driver.commands.poll_recv(cx) {
591 Poll::Ready(Some(command)) => {
592 let action = self
593 .driver
594 .webview
595 .borrow_mut()
596 .handle_driver_command(command);
597 action.run(&mut self.evaluate_script);
598 }
599 Poll::Ready(None) => return Poll::Ready(()),
600 Poll::Pending => return Poll::Pending,
601 }
602 }
603 }
604}
605
606fn blank_response() -> http::Response<Vec<u8>> {
608 http::Response::builder()
609 .status(200)
610 .body(vec![])
611 .expect("Failed to build blank response")
612}
613
614fn error_response() -> http::Response<Vec<u8>> {
616 http::Response::builder()
617 .status(400)
618 .body(vec![])
619 .expect("Failed to build error response")
620}
621
622fn module_response(content: &str) -> http::Response<Vec<u8>> {
624 http::Response::builder()
625 .status(200)
626 .header("Content-Type", "application/javascript")
627 .header("access-control-allow-origin", "*")
628 .body(content.as_bytes().to_vec())
629 .expect("Failed to build module response")
630}
631
632fn not_found_response() -> http::Response<Vec<u8>> {
634 http::Response::builder()
635 .status(404)
636 .body(b"Not Found".to_vec())
637 .expect("Failed to build not found response")
638}
639
640#[cfg(test)]
641mod tests {
642 use super::*;
643 use crate::ipc::{DecodedVariant, MessageType};
644 use std::sync::Arc;
645
646 fn ipc_message(message_type: MessageType) -> IPCMessage {
647 crate::ipc::empty_message(message_type)
648 }
649
650 fn handler_request(message_type: MessageType) -> http::Request<Vec<u8>> {
651 let engine = base64::engine::general_purpose::STANDARD;
652 let body_base64 = engine.encode(ipc_message(message_type).data());
653
654 http::Request::builder()
655 .uri("wry://index.html/__wbg__/handler")
656 .header("dioxus-data", body_base64)
657 .body(Vec::new())
658 .expect("failed to build request")
659 }
660
661 fn lock_request() -> http::Request<Vec<u8>> {
662 http::Request::builder()
663 .uri("wry://index.html/__wbg__/handler")
664 .header("wry-bindgen-lock", "1")
665 .body(Vec::new())
666 .expect("failed to build request")
667 }
668
669 fn initialized_request() -> http::Request<Vec<u8>> {
670 http::Request::builder()
671 .uri("wry://index.html/__wbg__/initialized")
672 .body(Vec::new())
673 .expect("failed to build request")
674 }
675
676 struct NoopWake;
677
678 impl std::task::Wake for NoopWake {
679 fn wake(self: Arc<Self>) {}
680 }
681
682 fn poll_forwarded_message(ipc: &WryIPC) -> IPCMessage {
683 let waker = std::task::Waker::from(Arc::new(NoopWake));
684 let mut cx = std::task::Context::from_waker(&waker);
685 match ipc.poll_recv(&mut cx) {
686 Poll::Ready(Some(Inbound::Message(msg))) => msg,
687 other => panic!("expected forwarded IPC message, got {other:?}"),
688 }
689 }
690
691 fn poll_driver(driver: &mut WryBindgenWebviewDriver) -> Poll<()> {
692 let waker = std::task::Waker::from(Arc::new(NoopWake));
693 let mut cx = std::task::Context::from_waker(&waker);
694 driver.poll(&mut cx)
695 }
696
697 #[test]
698 fn js_respond_is_forwarded_and_parks_xhr() {
699 let (ipc, sender, _driver_commands) = WryIPC::new();
700 let mut layer = WebviewMessageLayer::new(sender);
701 let responder_called = Rc::new(RefCell::new(false));
702 let captured_responder_called = responder_called.clone();
703
704 layer.receive_js_message(
705 ipc_message(MessageType::Respond),
706 WryBindgenResponder::from(move |_| {
707 *captured_responder_called.borrow_mut() = true;
708 }),
709 );
710
711 assert!(layer.current_xhr.is_some());
712 assert!(
713 !*responder_called.borrow(),
714 "JS response XHR should stay parked for Rust's next reply"
715 );
716 let received = poll_forwarded_message(&ipc);
717 assert!(matches!(
718 received.decoded().unwrap(),
719 DecodedVariant::Respond { .. }
720 ));
721 }
722
723 #[test]
724 fn js_message_while_xhr_is_parked_panics() {
725 let (_ipc, sender, _driver_commands) = WryIPC::new();
726 let mut layer = WebviewMessageLayer::new(sender);
727 layer.current_xhr = Some(WryBindgenResponder::from(|_| {}));
728
729 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
730 layer.receive_js_message(
731 ipc_message(MessageType::Evaluate),
732 WryBindgenResponder::from(|_| {}),
733 );
734 }));
735
736 assert!(result.is_err());
737 }
738
739 #[test]
740 fn lock_request_while_xhr_is_parked_panics() {
741 let (_ipc, sender, _driver_commands) = WryIPC::new();
742 let mut layer = WebviewMessageLayer::new(sender);
743 layer.current_xhr = Some(WryBindgenResponder::from(|_| {}));
744
745 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
746 layer.receive_lock_request(WryBindgenResponder::from(|_| {}));
747 }));
748
749 assert!(result.is_err());
750 }
751
752 #[test]
753 fn rust_outbound_messages_use_same_parked_xhr_response_path() {
754 for message_type in [MessageType::Evaluate, MessageType::Respond] {
755 let (_ipc, sender, _driver_commands) = WryIPC::new();
756 let mut layer = WebviewMessageLayer::new(sender);
757 let response = Rc::new(RefCell::new(None));
758 let captured_response = response.clone();
759 let message = ipc_message(message_type);
760 let expected_body = message.data().to_vec();
761
762 layer.current_xhr = Some(WryBindgenResponder::from(move |response| {
763 *captured_response.borrow_mut() = Some(response);
764 }));
765 layer.receive_rust_message(message);
766
767 assert!(layer.current_xhr.is_none());
768 let response = response
769 .borrow_mut()
770 .take()
771 .expect("parked XHR should receive Rust IPC");
772 assert_eq!(response.status(), http::StatusCode::OK);
773 let engine = base64::engine::general_purpose::STANDARD;
774 let body = engine
775 .decode(response.body())
776 .expect("response body should be base64 IPC bytes");
777 assert_eq!(body, expected_body);
778 }
779 }
780
781 #[test]
782 fn handler_responds_error_when_evaluate_arrives_after_runtime_drop() {
783 let bindgen = WryBindgen::new();
784 let protocol_handler = bindgen.protocol_handler();
785 drop(bindgen);
786
787 let response = Rc::new(RefCell::new(None));
788 let captured_response = response.clone();
789 let request = handler_request(MessageType::Evaluate);
790
791 let unhandled = protocol_handler.handle_request("wry", &request, move |response| {
792 *captured_response.borrow_mut() = Some(response)
793 });
794
795 assert!(unhandled.is_none());
796 let response = response
797 .borrow_mut()
798 .take()
799 .expect("closed runtime should receive an error response");
800 assert_eq!(response.status(), http::StatusCode::BAD_REQUEST);
801 }
802
803 #[test]
804 fn lock_request_is_queued_until_webview_loads() {
805 let bindgen = WryBindgen::new();
806 let protocol_handler = bindgen.protocol_handler();
807
808 let evaluated_scripts = Rc::new(RefCell::new(Vec::new()));
809 let captured_scripts = evaluated_scripts.clone();
810 let (runtime, driver) = bindgen.split();
811 let mut driver = driver.with_evaluate_script(move |script| {
812 captured_scripts.borrow_mut().push(script.to_string());
813 });
814
815 runtime.ipc.send_acquire_lock();
816 assert!(matches!(poll_driver(&mut driver), Poll::Pending));
817 assert!(evaluated_scripts.borrow().is_empty());
818
819 let response = Rc::new(RefCell::new(None));
820 let captured_response = response.clone();
821 let request = initialized_request();
822 let unhandled = protocol_handler.handle_request("wry", &request, move |response| {
823 *captured_response.borrow_mut() = Some(response)
824 });
825
826 assert!(unhandled.is_none());
827 assert_eq!(
828 response.borrow().as_ref().unwrap().status(),
829 http::StatusCode::OK
830 );
831 assert!(matches!(poll_driver(&mut driver), Poll::Pending));
832 assert_eq!(
833 evaluated_scripts.borrow().as_slice(),
834 ["window.__wry_acquire_handler_lock()"]
835 );
836 }
837
838 #[test]
839 fn lock_request_while_js_xhr_is_parked_is_not_dropped_or_duplicated() {
840 let bindgen = WryBindgen::new();
841 let protocol_handler = bindgen.protocol_handler();
842
843 let evaluated_scripts = Rc::new(RefCell::new(Vec::new()));
844 let captured_scripts = evaluated_scripts.clone();
845 let (runtime, driver) = bindgen.split();
846 let mut driver = driver.with_evaluate_script(move |script| {
847 captured_scripts.borrow_mut().push(script.to_string());
848 });
849
850 let request = initialized_request();
851 let unhandled = protocol_handler.handle_request("wry", &request, |_| {});
852 assert!(unhandled.is_none());
853
854 let response = Rc::new(RefCell::new(None));
855 let captured_response = response.clone();
856 let request = handler_request(MessageType::Evaluate);
857
858 let unhandled = protocol_handler.handle_request("wry", &request, move |response| {
859 *captured_response.borrow_mut() = Some(response)
860 });
861
862 assert!(unhandled.is_none());
863 assert!(
864 response.borrow().is_none(),
865 "JS callback XHR should stay parked while Rust handles it"
866 );
867
868 runtime.ipc.send_acquire_lock();
869 assert!(matches!(poll_driver(&mut driver), Poll::Pending));
870 assert_eq!(
871 evaluated_scripts.borrow().as_slice(),
872 ["window.__wry_acquire_handler_lock()"],
873 "lock script should be requested while the parked XHR is outstanding"
874 );
875
876 runtime.ipc.send_ipc(ipc_message(MessageType::Respond));
877 assert!(matches!(poll_driver(&mut driver), Poll::Pending));
878
879 let response = response
880 .borrow_mut()
881 .take()
882 .expect("parked JS callback XHR should receive Rust's response");
883 assert_eq!(response.status(), http::StatusCode::OK);
884 assert_eq!(
885 evaluated_scripts.borrow().as_slice(),
886 ["window.__wry_acquire_handler_lock()"],
887 "answering the parked XHR should not duplicate the in-flight lock request"
888 );
889 }
890
891 #[test]
892 fn handler_responds_error_when_lock_arrives_after_runtime_drop() {
893 let bindgen = WryBindgen::new();
894 let protocol_handler = bindgen.protocol_handler();
895 drop(bindgen);
896
897 let response = Rc::new(RefCell::new(None));
898 let captured_response = response.clone();
899 let request = lock_request();
900
901 let unhandled = protocol_handler.handle_request("wry", &request, move |response| {
902 *captured_response.borrow_mut() = Some(response)
903 });
904
905 assert!(unhandled.is_none());
906 let response = response
907 .borrow_mut()
908 .take()
909 .expect("closed runtime should receive an error response");
910 assert_eq!(response.status(), http::StatusCode::BAD_REQUEST);
911 }
912}