summaryrefslogtreecommitdiffstats
path: root/services/common/app_services_logger/src/lib.rs
blob: 92125b62c4baa58b626edf4debc76d5ed608173a (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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/* 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 https://mozilla.org/MPL/2.0/. */

//! This provides a XPCOM service to send app services logs to the desktop

#[macro_use]
extern crate cstr;

#[macro_use]
extern crate xpcom;

use golden_gate::log::LogSink;
use nserror::{nsresult, NS_OK};
use nsstring::nsAString;
use once_cell::sync::Lazy;
use std::os::raw::c_char;
use std::{
    collections::HashMap,
    sync::{
        atomic::{AtomicBool, Ordering},
        RwLock,
    },
};
use xpcom::{
    interfaces::{mozIAppServicesLogger, mozIServicesLogSink, nsIObserverService, nsISupports},
    RefPtr,
};

/// A flag that's set after we register our observer to clear the map of loggers
/// on shutdown.
static SHUTDOWN_OBSERVED: AtomicBool = AtomicBool::new(false);

#[xpcom(implement(mozIAppServicesLogger), nonatomic)]
pub struct AppServicesLogger {}

pub static LOGGERS_BY_TARGET: Lazy<RwLock<HashMap<String, LogSink>>> = Lazy::new(|| {
    let h: HashMap<String, LogSink> = HashMap::new();
    let m = RwLock::new(h);
    m
});

impl AppServicesLogger {
    xpcom_method!(register => Register(target: *const nsAString, logger: *const mozIServicesLogSink));
    fn register(&self, target: &nsAString, logger: &mozIServicesLogSink) -> Result<(), nsresult> {
        let log_sink_logger = LogSink::with_logger(Some(logger))?;

        ensure_observing_shutdown();

        LOGGERS_BY_TARGET
            .write()
            .unwrap()
            .insert(target.to_string(), log_sink_logger);
        Ok(())
    }

    pub fn is_app_services_logger_registered(target: String) -> bool {
        match LOGGERS_BY_TARGET.read() {
            Ok(loggers_by_target) => loggers_by_target.contains_key(&target),
            Err(_e) => false,
        }
    }
}

// Import the `NS_IsMainThread` symbol from Gecko...
extern "C" {
    fn NS_IsMainThread() -> bool;
}

/// Registers an observer to clear the loggers map on `xpcom-shutdown`. This
/// function must be called from the main thread, because the observer service
//// is main thread-only.
fn ensure_observing_shutdown() {
    assert!(unsafe { NS_IsMainThread() });
    // If we've already registered our observer, bail. Relaxed ordering is safe
    // here and below, because we've asserted we're only called from the main
    // thread, and only check the flag here.
    if SHUTDOWN_OBSERVED.load(Ordering::Relaxed) {
        return;
    }
    if let Ok(service) = xpcom::components::Observer::service::<nsIObserverService>() {
        let observer = ShutdownObserver::allocate(InitShutdownObserver {});
        let rv = unsafe {
            service.AddObserver(observer.coerce(), cstr!("xpcom-shutdown").as_ptr(), false)
        };
        // If we fail to register the observer now, or fail to get the observer
        // service, the flag will remain `false`, and we'll try again on the
        // next call to `ensure_observing_shutdown`.
        SHUTDOWN_OBSERVED.store(rv.succeeded(), Ordering::Relaxed);
    }
}

#[xpcom(implement(nsIObserver), nonatomic)]
struct ShutdownObserver {}

impl ShutdownObserver {
    xpcom_method!(observe => Observe(_subject: *const nsISupports, topic: *const c_char, _data: *const u16));
    /// Remove our shutdown observer and clear the map.
    fn observe(
        &self,
        _subject: &nsISupports,
        topic: *const c_char,
        _data: *const u16,
    ) -> Result<(), nsresult> {
        LOGGERS_BY_TARGET.write().unwrap().clear();
        if let Ok(service) = xpcom::components::Observer::service::<nsIObserverService>() {
            // Ignore errors, since we're already shutting down.
            let _ = unsafe { service.RemoveObserver(self.coerce(), topic) };
        }
        Ok(())
    }
}

/// The constructor for an `AppServicesLogger` service. This uses C linkage so that it
/// can be called from C++. See `AppServicesLoggerComponents.h` for the C++
/// constructor that's passed to the component manager.
///
/// # Safety
///
/// This function is unsafe because it dereferences `result`.
#[no_mangle]
pub unsafe extern "C" fn NS_NewAppServicesLogger(
    result: *mut *const mozIAppServicesLogger,
) -> nsresult {
    let logger = AppServicesLogger::allocate(InitAppServicesLogger {});
    RefPtr::new(logger.coerce::<mozIAppServicesLogger>()).forget(&mut *result);
    NS_OK
}