/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ extern crate nsstring; use cstr::cstr; use nserror::{nsresult, NS_ERROR_SERVICE_NOT_AVAILABLE, NS_ERROR_UNEXPECTED, NS_OK}; use nsstring::*; use std::cell::RefCell; use std::future::Future; use xpcom::{interfaces::nsIThreadManager, xpcom, xpcom_method}; #[xpcom(implement(nsINestedEventLoopCondition), nonatomic)] struct FutureCompleteCondition { value: RefCell>, } impl FutureCompleteCondition { xpcom_method!(is_done => IsDone() -> bool); fn is_done(&self) -> Result { Ok(self.value.borrow().is_some()) } } /// Spin the event loop on the current thread until `future` is resolved. /// /// # Safety /// /// Spinning a nested event loop should always be avoided when possible, as it /// can cause hangs, break JS run-to-completion guarantees, and break other C++ /// code currently on the stack relying on heap invariants. While in a pure-rust /// codebase this method would only be ill-advised and not technically "unsafe", /// it is marked as unsafe due to the potential for triggering unsafety in /// unrelated C++ code. pub unsafe fn spin_event_loop_until( reason: &'static str, future: F, ) -> Result where F: Future + 'static, F::Output: 'static, { let thread_manager = xpcom::get_service::(cstr!("@mozilla.org/thread-manager;1")) .ok_or(NS_ERROR_SERVICE_NOT_AVAILABLE)?; let cond = FutureCompleteCondition::::allocate(InitFutureCompleteCondition { value: RefCell::new(None), }); // Spawn our future onto the current thread event loop, and record the // completed value as it completes. let cond2 = cond.clone(); crate::spawn_local(reason, async move { let rv = future.await; *cond2.value.borrow_mut() = Some(rv); }) .detach(); thread_manager .SpinEventLoopUntil(&*nsCStr::from(reason), cond.coerce()) .to_result()?; let rv = cond.value.borrow_mut().take(); rv.ok_or(NS_ERROR_UNEXPECTED) }