summaryrefslogtreecommitdiffstats
path: root/xpcom/rust/moz_task/src/event_loop.rs
blob: b07cb5412ff60845320dbad9e96400be682e22c0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/* 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/. */

use cstr::cstr;
use nserror::{nsresult, NS_ERROR_SERVICE_NOT_AVAILABLE, NS_OK};
use std::cell::UnsafeCell;
use xpcom::{interfaces::nsIThreadManager, xpcom, xpcom_method};

type IsDoneClosure = dyn FnMut() -> bool + 'static;

#[derive(xpcom)]
#[xpimplements(nsINestedEventLoopCondition)]
#[refcnt = "atomic"]
struct InitEventLoopCondition {
    closure: UnsafeCell<Box<IsDoneClosure>>,
}

impl EventLoopCondition {
    xpcom_method!(is_done => IsDone() -> bool);
    fn is_done(&self) -> Result<bool, nsresult> {
        unsafe { Ok((&mut *self.closure.get())()) }
    }
}

/// Spin the event loop on the current thread until `pred` returns true.
///
/// # 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<P>(pred: P) -> Result<(), nsresult>
where
    P: FnMut() -> bool + 'static,
{
    let closure = Box::new(pred) as Box<IsDoneClosure>;
    let cond = EventLoopCondition::allocate(InitEventLoopCondition {
        closure: UnsafeCell::new(closure),
    });
    let thread_manager =
        xpcom::get_service::<nsIThreadManager>(cstr!("@mozilla.org/thread-manager;1"))
            .ok_or(NS_ERROR_SERVICE_NOT_AVAILABLE)?;

    thread_manager.SpinEventLoopUntil(cond.coerce()).to_result()
}