use std::cell::RefCell;
use std::rc::Rc;
use std::time::Duration;
use block2::RcBlock;
use objc2::runtime::AnyObject;
use objc2_foundation::{NSError, NSString};
use objc2_web_kit::WKWebView;
use crate::engine::{EngineError, EngineResult};
pub(super) fn run_loop_until<F: FnMut() -> bool>(mut done: F, budget: Duration) -> bool {
use objc2_foundation::{NSDate, NSDefaultRunLoopMode, NSRunLoop};
let deadline = std::time::Instant::now() + budget;
let run_loop = NSRunLoop::currentRunLoop();
while std::time::Instant::now() < deadline {
if done() {
return true;
}
let slice = NSDate::dateWithTimeIntervalSinceNow(0.05);
unsafe { run_loop.runMode_beforeDate(NSDefaultRunLoopMode, &slice) };
}
done()
}
pub(super) fn eval_js_string(
web_view: &WKWebView,
js: &str,
budget: Duration,
) -> EngineResult<String> {
let slot: Rc<RefCell<Option<Result<String, String>>>> = Rc::new(RefCell::new(None));
let slot_for_block = slot.clone();
let block = RcBlock::new(move |result: *mut AnyObject, error: *mut NSError| {
if !error.is_null() {
let err = unsafe { &*error };
*slot_for_block.borrow_mut() = Some(Err(err.localizedDescription().to_string()));
return;
}
if result.is_null() {
*slot_for_block.borrow_mut() = Some(Ok(String::new()));
return;
}
let s: &NSString = unsafe { &*(result as *const NSString) };
*slot_for_block.borrow_mut() = Some(Ok(s.to_string()));
});
let ns_js = NSString::from_str(js);
unsafe {
web_view.evaluateJavaScript_completionHandler(&ns_js, Some(&block));
}
let slot_check = slot.clone();
let ok = run_loop_until(move || slot_check.borrow().is_some(), budget);
if !ok {
return Err(EngineError::Timeout {
budget,
primitive: "eval",
});
}
let result = slot.borrow_mut().take();
match result {
Some(Ok(s)) => Ok(s),
Some(Err(msg)) => Err(EngineError::Other(format!("eval failed: {msg}"))),
None => unreachable!(),
}
}