summaryrefslogtreecommitdiffstats
path: root/toolkit/components/glean/src/init/user_activity.rs
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/glean/src/init/user_activity.rs')
-rw-r--r--toolkit/components/glean/src/init/user_activity.rs129
1 files changed, 129 insertions, 0 deletions
diff --git a/toolkit/components/glean/src/init/user_activity.rs b/toolkit/components/glean/src/init/user_activity.rs
new file mode 100644
index 0000000000..dbafeca4e8
--- /dev/null
+++ b/toolkit/components/glean/src/init/user_activity.rs
@@ -0,0 +1,129 @@
+// 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/.
+
+use std::ffi::CStr;
+use std::os::raw::c_char;
+use std::sync::{
+ atomic::{AtomicBool, Ordering},
+ RwLock,
+};
+use std::time::{Duration, Instant};
+
+use nserror::{nsresult, NS_ERROR_FAILURE, NS_OK};
+use xpcom::{
+ interfaces::{nsIObserverService, nsISupports},
+ RefPtr,
+};
+
+// Partially cargo-culted from UploadPrefObserver.
+#[xpcom(implement(nsIObserver), atomic)]
+pub(crate) struct UserActivityObserver {
+ last_edge: RwLock<Instant>,
+ was_active: AtomicBool,
+}
+
+/// Listens to Firefox Desktop's `user-interaction-(in)active` topics,
+/// debouncing them before calling into the Glean SDK Client Activity API.
+/// See
+/// [the docs](https://firefox-source-docs.mozilla.org/toolkit/components/glean/builtin_pings.html)
+/// for more info.
+#[allow(non_snake_case)]
+impl UserActivityObserver {
+ pub(crate) fn begin_observing() -> Result<(), nsresult> {
+ // First and foremost, even if we can't get the ObserverService,
+ // init always means client activity.
+ glean::handle_client_active();
+
+ // SAFETY: Everything here is self-contained.
+ //
+ // * We allocate the activity observer, created by the xpcom macro
+ // * We create cstr from a static string.
+ // * We control all input to `AddObserver`
+ unsafe {
+ let activity_obs = Self::allocate(InitUserActivityObserver {
+ last_edge: RwLock::new(Instant::now()),
+ was_active: AtomicBool::new(false),
+ });
+ let obs_service: RefPtr<nsIObserverService> =
+ xpcom::components::Observer::service().map_err(|_| NS_ERROR_FAILURE)?;
+ let rv = obs_service.AddObserver(
+ activity_obs.coerce(),
+ cstr!("user-interaction-active").as_ptr(),
+ false,
+ );
+ if !rv.succeeded() {
+ return Err(rv);
+ }
+ let rv = obs_service.AddObserver(
+ activity_obs.coerce(),
+ cstr!("user-interaction-inactive").as_ptr(),
+ false,
+ );
+ if !rv.succeeded() {
+ return Err(rv);
+ }
+ }
+ Ok(())
+ }
+
+ unsafe fn Observe(
+ &self,
+ _subject: *const nsISupports,
+ topic: *const c_char,
+ _data: *const u16,
+ ) -> nserror::nsresult {
+ match CStr::from_ptr(topic).to_str() {
+ Ok("user-interaction-active") => self.handle_active(),
+ Ok("user-interaction-inactive") => self.handle_inactive(),
+ _ => NS_OK,
+ }
+ }
+
+ fn handle_active(&self) -> nserror::nsresult {
+ let was_active = self.was_active.swap(true, Ordering::SeqCst);
+ if !was_active {
+ let inactivity = self
+ .last_edge
+ .read()
+ .expect("Edge lock poisoned.")
+ .elapsed();
+ // We only care after a certain period of inactivity (default 20min).
+ let limit = static_prefs::pref!("telemetry.fog.test.inactivity_limit");
+ if inactivity >= Duration::from_secs(limit.into()) {
+ log::info!(
+ "User triggers core activity after {}s!",
+ inactivity.as_secs()
+ );
+ glean::handle_client_active();
+ }
+ let mut edge = self.last_edge.write().expect("Edge lock poisoned.");
+ *edge = Instant::now();
+ }
+ NS_OK
+ }
+
+ fn handle_inactive(&self) -> nserror::nsresult {
+ let was_active = self.was_active.swap(false, Ordering::SeqCst);
+ // This is actually always so. Inactivity is only notified once.
+ if was_active {
+ let activity = self
+ .last_edge
+ .read()
+ .expect("Edge lock poisoned.")
+ .elapsed();
+ // We only care after a certain period of activity (default 2min).
+ let limit = static_prefs::pref!("telemetry.fog.test.activity_limit");
+ if activity >= Duration::from_secs(limit.into()) {
+ log::info!(
+ "User triggers core inactivity after {}s!",
+ activity.as_secs()
+ );
+ glean::handle_client_inactive();
+ }
+ let mut edge = self.last_edge.write().expect("Edge lock poisoned.");
+ *edge = Instant::now();
+ }
+ NS_OK
+ }
+}