summaryrefslogtreecommitdiffstats
path: root/services/common/app_services_logger/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'services/common/app_services_logger/src/lib.rs')
-rw-r--r--services/common/app_services_logger/src/lib.rs128
1 files changed, 128 insertions, 0 deletions
diff --git a/services/common/app_services_logger/src/lib.rs b/services/common/app_services_logger/src/lib.rs
new file mode 100644
index 0000000000..92125b62c4
--- /dev/null
+++ b/services/common/app_services_logger/src/lib.rs
@@ -0,0 +1,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
+}