diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /toolkit/components/glean/src/init/user_activity.rs | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/glean/src/init/user_activity.rs')
-rw-r--r-- | toolkit/components/glean/src/init/user_activity.rs | 129 |
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 + } +} |