22// License, v. 2.0. If a copy of the MPL was not distributed with this
33// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44#[ cfg( feature = "stateful" ) ]
5- use crate :: stateful:: gecko_prefs:: { OriginalGeckoPref , PrefUnenrollReason } ;
5+ use crate :: stateful:: gecko_prefs:: { GeckoPrefStore , OriginalGeckoPref , PrefUnenrollReason } ;
66use crate :: {
77 defaults:: Defaults ,
88 error:: { debug, warn, NimbusError , Result } ,
@@ -11,6 +11,8 @@ use crate::{
1111 SLUG_REPLACEMENT_PATTERN ,
1212} ;
1313use serde_derive:: * ;
14+ #[ cfg( feature = "stateful" ) ]
15+ use std:: sync:: Arc ;
1416use std:: {
1517 collections:: { HashMap , HashSet } ,
1618 fmt:: { Display , Formatter , Result as FmtResult } ,
@@ -154,6 +156,24 @@ pub enum PreviousState {
154156 GeckoPref ( PreviousGeckoPrefState ) ,
155157}
156158
159+ #[ cfg( feature = "stateful" ) ]
160+ impl PreviousState {
161+ #[ cfg( feature = "stateful" ) ]
162+ pub ( crate ) fn on_revert_to_previous_state (
163+ & self ,
164+ #[ cfg( feature = "stateful" ) ] gecko_pref_store : & Option < Arc < GeckoPrefStore > > ,
165+ ) {
166+ match self {
167+ PreviousState :: GeckoPref ( previous_gecko_pref_state) => {
168+ if let Some ( store) = gecko_pref_store {
169+ store. handler . set_gecko_prefs_original_values (
170+ previous_gecko_pref_state. original_values . clone ( ) ,
171+ )
172+ }
173+ }
174+ }
175+ }
176+ }
157177// Every experiment has an ExperimentEnrollment, even when we aren't enrolled.
158178// ⚠️ Attention : Changes to this type should be accompanied by a new test ⚠️
159179// ⚠️ in `src/stateful/tests/test_enrollment_bw_compat.rs` below, and may require a DB migration. ⚠️
@@ -241,6 +261,7 @@ impl ExperimentEnrollment {
241261 available_randomization_units : & AvailableRandomizationUnits ,
242262 updated_experiment : & Experiment ,
243263 targeting_helper : & NimbusTargetingHelper ,
264+ #[ cfg( feature = "stateful" ) ] gecko_pref_store : & Option < Arc < GeckoPrefStore > > ,
244265 out_enrollment_events : & mut Vec < EnrollmentChangeEvent > ,
245266 ) -> Result < Self > {
246267 Ok ( match & self . status {
@@ -274,6 +295,14 @@ impl ExperimentEnrollment {
274295 "Existing experiment enrollment '{}' is now disqualified (global opt-out)" ,
275296 & self . slug
276297 ) ;
298+ #[ cfg( feature = "stateful" ) ]
299+ if let EnrollmentStatus :: Enrolled {
300+ previous_state : Some ( previous_state) ,
301+ ..
302+ } = & self . status
303+ {
304+ previous_state. on_revert_to_previous_state ( gecko_pref_store) ;
305+ }
277306 let updated_enrollment =
278307 self . disqualify_from_enrolled ( DisqualifiedReason :: OptOut ) ;
279308 out_enrollment_events. push ( updated_enrollment. get_change_event ( ) ) ;
@@ -294,6 +323,17 @@ impl ExperimentEnrollment {
294323 updated_experiment,
295324 targeting_helper,
296325 ) ?;
326+
327+ #[ cfg( feature = "stateful" ) ]
328+ if self . will_pref_experiment_change ( updated_experiment, & evaluated_enrollment) {
329+ if let EnrollmentStatus :: Enrolled {
330+ previous_state : Some ( previous_state) ,
331+ ..
332+ } = & self . status
333+ {
334+ previous_state. on_revert_to_previous_state ( gecko_pref_store) ;
335+ }
336+ }
297337 match evaluated_enrollment. status {
298338 EnrollmentStatus :: Error { .. } => {
299339 let updated_enrollment =
@@ -378,6 +418,7 @@ impl ExperimentEnrollment {
378418 /// from the database after `PREVIOUS_ENROLLMENTS_GC_TIME`.
379419 fn on_experiment_ended (
380420 & self ,
421+ #[ cfg( feature = "stateful" ) ] gecko_prefs : & Option < Arc < GeckoPrefStore > > ,
381422 out_enrollment_events : & mut Vec < EnrollmentChangeEvent > ,
382423 ) -> Option < Self > {
383424 debug ! (
@@ -391,6 +432,16 @@ impl ExperimentEnrollment {
391432 | EnrollmentStatus :: WasEnrolled { .. }
392433 | EnrollmentStatus :: Error { .. } => return None , // We were never enrolled anyway, simply delete the enrollment record from the DB.
393434 } ;
435+
436+ #[ cfg( feature = "stateful" ) ]
437+ if let EnrollmentStatus :: Enrolled {
438+ previous_state : Some ( previous_state) ,
439+ ..
440+ } = & self . status
441+ {
442+ previous_state. on_revert_to_previous_state ( gecko_prefs) ;
443+ }
444+
394445 let enrollment = Self {
395446 slug : self . slug . clone ( ) ,
396447 status : EnrollmentStatus :: WasEnrolled {
@@ -408,9 +459,18 @@ impl ExperimentEnrollment {
408459 pub ( crate ) fn on_explicit_opt_out (
409460 & self ,
410461 out_enrollment_events : & mut Vec < EnrollmentChangeEvent > ,
462+ #[ cfg( feature = "stateful" ) ] gecko_prefs : & Option < Arc < GeckoPrefStore > > ,
411463 ) -> ExperimentEnrollment {
412- match self . status {
413- EnrollmentStatus :: Enrolled { .. } => {
464+ match & self . status {
465+ EnrollmentStatus :: Enrolled {
466+ #[ cfg( feature = "stateful" ) ]
467+ previous_state,
468+ ..
469+ } => {
470+ #[ cfg( feature = "stateful" ) ]
471+ if let Some ( previous_state) = previous_state {
472+ previous_state. on_revert_to_previous_state ( gecko_prefs) ;
473+ }
414474 let enrollment = self . disqualify_from_enrolled ( DisqualifiedReason :: OptOut ) ;
415475 out_enrollment_events. push ( enrollment. get_change_event ( ) ) ;
416476 enrollment
@@ -561,6 +621,57 @@ impl ExperimentEnrollment {
561621 | EnrollmentStatus :: Error { .. } => self . clone ( ) ,
562622 }
563623 }
624+
625+ #[ cfg( feature = "stateful" ) ]
626+ pub ( crate ) fn will_pref_experiment_change (
627+ & self ,
628+ updated_experiment : & Experiment ,
629+ updated_enrollment : & ExperimentEnrollment ,
630+ ) -> bool {
631+ let ( original_feature_id, original_branch_slug) = match & self . status {
632+ EnrollmentStatus :: Enrolled {
633+ previous_state : Some ( PreviousState :: GeckoPref ( previous_state) ) ,
634+ branch,
635+ ..
636+ } => ( & previous_state. feature_id , branch) ,
637+ // Can't change if it isn't a pref experiment
638+ _ => {
639+ return false ;
640+ }
641+ } ;
642+
643+ let updated_branch_slug = match & updated_enrollment. status {
644+ EnrollmentStatus :: Enrolled { branch, .. } => branch,
645+ // If we are no longer going to be enrolled, then a change happened
646+ _ => {
647+ return true ;
648+ }
649+ } ;
650+
651+ // Branch changed
652+ if updated_branch_slug != original_branch_slug {
653+ return true ;
654+ }
655+
656+ let Some ( branch) = updated_experiment. get_branch ( updated_branch_slug) else {
657+ return true ;
658+ } ;
659+
660+ // Feature changed
661+ match & branch. feature {
662+ Some ( updated_feature) => {
663+ if updated_feature. feature_id != * original_feature_id {
664+ return true ;
665+ }
666+ }
667+ None => {
668+ return true ;
669+ }
670+ } ;
671+
672+ // ToDo: In review ask about checking variable - unsure of what that corresponds to.
673+ false
674+ }
564675}
565676
566677// ⚠️ Attention : Changes to this type should be accompanied by a new test ⚠️
@@ -656,6 +767,7 @@ impl<'a> EnrollmentsEvolver<'a> {
656767 prev_experiments : & [ E ] ,
657768 next_experiments : & [ Experiment ] ,
658769 prev_enrollments : & [ ExperimentEnrollment ] ,
770+ #[ cfg( feature = "stateful" ) ] gecko_pref_store : & Option < Arc < GeckoPrefStore > > ,
659771 ) -> Result < ( Vec < ExperimentEnrollment > , Vec < EnrollmentChangeEvent > ) >
660772 where
661773 E : ExperimentMetadata + Clone ,
@@ -677,6 +789,8 @@ impl<'a> EnrollmentsEvolver<'a> {
677789 & prev_rollouts,
678790 & next_rollouts,
679791 & ro_enrollments,
792+ #[ cfg( feature = "stateful" ) ]
793+ gecko_pref_store,
680794 ) ?;
681795
682796 enrollments. extend ( next_ro_enrollments) ;
@@ -701,6 +815,8 @@ impl<'a> EnrollmentsEvolver<'a> {
701815 & prev_experiments,
702816 & next_experiments,
703817 & prev_enrollments,
818+ #[ cfg( feature = "stateful" ) ]
819+ gecko_pref_store,
704820 ) ?;
705821
706822 enrollments. extend ( next_exp_enrollments) ;
@@ -717,6 +833,7 @@ impl<'a> EnrollmentsEvolver<'a> {
717833 prev_experiments : & [ E ] ,
718834 next_experiments : & [ Experiment ] ,
719835 prev_enrollments : & [ ExperimentEnrollment ] ,
836+ #[ cfg( feature = "stateful" ) ] gecko_pref_store : & Option < Arc < GeckoPrefStore > > ,
720837 ) -> Result < ( Vec < ExperimentEnrollment > , Vec < EnrollmentChangeEvent > ) >
721838 where
722839 E : ExperimentMetadata + Clone ,
@@ -756,6 +873,8 @@ impl<'a> EnrollmentsEvolver<'a> {
756873 next_experiments_map. get ( slug) . copied ( ) ,
757874 Some ( prev_enrollment) ,
758875 & mut enrollment_events,
876+ #[ cfg( feature = "stateful" ) ]
877+ gecko_pref_store,
759878 ) {
760879 Ok ( enrollment) => enrollment,
761880 Err ( e) => {
@@ -857,6 +976,8 @@ impl<'a> EnrollmentsEvolver<'a> {
857976 Some ( next_experiment) ,
858977 prev_enrollment,
859978 & mut enrollment_events,
979+ #[ cfg( feature = "stateful" ) ]
980+ gecko_pref_store,
860981 ) {
861982 Ok ( enrollment) => enrollment,
862983 Err ( e) => {
@@ -950,6 +1071,7 @@ impl<'a> EnrollmentsEvolver<'a> {
9501071 next_experiment : Option < & Experiment > ,
9511072 prev_enrollment : Option < & ExperimentEnrollment > ,
9521073 out_enrollment_events : & mut Vec < EnrollmentChangeEvent > , // out param containing the events we'd like to emit to glean.
1074+ #[ cfg( feature = "stateful" ) ] gecko_pref_store : & Option < Arc < GeckoPrefStore > > ,
9531075 ) -> Result < Option < ExperimentEnrollment > >
9541076 where
9551077 E : ExperimentMetadata + Clone ,
@@ -978,16 +1100,20 @@ impl<'a> EnrollmentsEvolver<'a> {
9781100 out_enrollment_events,
9791101 ) ?) ,
9801102 // Experiment deleted remotely.
981- ( Some ( _) , None , Some ( enrollment) ) => {
982- enrollment. on_experiment_ended ( out_enrollment_events)
983- }
1103+ ( Some ( _) , None , Some ( enrollment) ) => enrollment. on_experiment_ended (
1104+ #[ cfg( feature = "stateful" ) ]
1105+ gecko_pref_store,
1106+ out_enrollment_events,
1107+ ) ,
9841108 // Known experiment.
9851109 ( Some ( _) , Some ( experiment) , Some ( enrollment) ) => {
9861110 Some ( enrollment. on_experiment_updated (
9871111 is_user_participating,
9881112 self . available_randomization_units ,
9891113 experiment,
9901114 & targeting_helper,
1115+ #[ cfg( feature = "stateful" ) ]
1116+ gecko_pref_store,
9911117 out_enrollment_events,
9921118 ) ?)
9931119 }
0 commit comments