// 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};
use nserror::{nsresult, NS_ERROR_FAILURE, NS_OK};
use nsstring::{nsACString, nsCStr};
use xpcom::{
interfaces::{nsIPrefBranch, nsISupports},
RefPtr,
};
/// Whether the current value of the localhost testing pref is permitting
/// metric recording (even if upload is disabled).
static RECORDING_ENABLED: AtomicBool = AtomicBool::new(
false);
// Partially cargo-culted from https://searchfox.org/mozilla-central/rev/598e50d2c3cd81cd616654f16af811adceb08f9f/security/manager/ssl/cert_storage/src/lib.rs#1192
#[xpcom(implement(nsIObserver), atomic)]
pub(
crate)
struct UploadPrefObserver {}
#[allow(non_snake_case)]
impl UploadPrefObserver {
pub(
crate)
fn begin_observing() -> Result<(), nsresult> {
// Ensure we begin with the correct current value of RECORDING_ENABLED.
let recording_enabled = static_prefs::pref!(
"telemetry.fog.test.localhost_port") <
0;
RECORDING_ENABLED.store(recording_enabled, Ordering::SeqCst);
// SAFETY: Everything here is self-contained.
//
// * We allocate the pref observer, created by the xpcom macro
// * We query the pref service and bail out if it doesn't exist.
// * We create a nsCStr from a static string.
// * We control all input to `AddObserverImpl`
unsafe {
let pref_obs =
Self::allocate(InitUploadPrefObserver {});
let pref_branch: RefPtr<nsIPrefBranch> =
xpcom::components::Preferences::service().map_err(|_| NS_ERROR_FAILURE)?;
let pref_nscstr =
&nsCStr::from(
"datareporting.healthreport.uploadEnabled")
as &nsACString;
(*pref_branch)
.AddObserverImpl(pref_nscstr, pref_obs.coerce(),
false)
.to_result()?;
let pref_nscstr = &nsCStr::from(
"telemetry.fog.test.localhost_port")
as &nsACString;
(*pref_branch)
.AddObserverImpl(pref_nscstr, pref_obs.coerce(),
false)
.to_result()?;
}
Ok(())
}
unsafe fn Observe(
&
self,
_subject: *
const nsISupports,
topic: *
const c_char,
pref_name: *
const u16,
) -> nserror::nsresult {
let topic = CStr::from_ptr(topic).to_str().unwrap();
// Conversion utf16 to utf8 is messy.
// We should only ever observe changes to one of the two prefs we want,
// but just to be on the safe side let's assert.
// cargo-culted from https://searchfox.org/mozilla-central/rev/598e50d2c3cd81cd616654f16af811adceb08f9f/security/manager/ssl/cert_storage/src/lib.rs#1606-1612
// (with a little transformation)
let len = (
0..).take_while(|&i| *pref_name.offset(i) !=
0).count();
// find NUL.
let slice = std::slice::from_raw_parts(pref_name, len);
let pref_name =
match String::from_utf16(slice) {
Ok(name) => name,
Err(_) =>
return NS_ERROR_FAILURE,
};
log::info!(
"Observed {:?}, {:?}", topic, pref_name);
debug_assert!(topic ==
"nsPref:changed");
debug_assert!(
pref_name ==
"datareporting.healthreport.uploadEnabled"
|| pref_name ==
"telemetry.fog.test.localhost_port"
);
let upload_enabled = static_prefs::pref!(
"datareporting.healthreport.uploadEnabled");
let recording_enabled = static_prefs::pref!("telemetry.fog.test.localhost_port") < 0;
log::info!(
"New upload_enabled {}, recording_enabled {}",
upload_enabled,
recording_enabled
);
if RECORDING_ENABLED.load(Ordering::SeqCst) && !recording_enabled {
// Whenever the test pref goes from permitting recording to forbidding it,
// ensure Glean is told to wipe the stores.
// This may send a "deletion-request" ping for a client_id that's never sent
// any other pings.
glean::set_collection_enabled(false);
}
RECORDING_ENABLED.store(recording_enabled, Ordering::SeqCst);
glean::set_collection_enabled(upload_enabled || recording_enabled);
NS_OK
}
}