Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# v156.0 (In progress)

## ✨ What's Changed ✨

### Nimbus

- The `pref-key` annotation for FML feature variables has been removed. ([#TODO](TODO))

[Full Changelog](In progress)

# v155.0 (_2026-08-13_)
Expand Down Expand Up @@ -36,7 +42,7 @@ In the cases where the failed before, they're now no-ops.

### Remote Settings
- Replacing v1 routes with v2 routes, removing added v2 routes ([#7492](https://github.com/mozilla/application-services/pull/7339))
- Verify signature of imported data when `.get()` is called with `sync_if_empty: true` ([#7518](https://github.com/mozilla/application-services/pull/7518))
- Verify signature of imported data when `.get()` is called with `sync_if_empty: true` ([#7518](https://github.com/mozilla/application-services/pull/7518))
- Do not quote `_since` values with the v2 API ([#7523](https://github.com/mozilla/application-services/pull/7523))

### Sync Manager
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,18 +182,9 @@ interface FMLObjectInterface {
}

/**
* A bare-bones interface for the FML generated features.
* A marker interface for FML generated feature classes.
*
* App developers should use the generated concrete classes, which
* implement this interface.
*/
interface FMLFeatureInterface : FMLObjectInterface {
/**
* A test if the feature configuration has been modified somehow, invalidating any experiment
* that uses it.
*
* This may be `true` if a `pref-key` has been set in the feature manifest and the user has
* set that preference.
*/
fun isModified(): Boolean = false
}
interface FMLFeatureInterface : FMLObjectInterface {}
8 changes: 0 additions & 8 deletions components/support/nimbus-fml/fixtures/fe/misc-features.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,6 @@ about:
channels:
- debug
features:
onboarding:
description: A feature containing a field with pref-key
variables:
enabled:
description: If true, enable new style onboarding.
type: Boolean
pref-key: enrollment_enabled
default: false
messaging:
description: A feature allowing coenrollment
allow-coenrollment: true
Expand Down
35 changes: 0 additions & 35 deletions components/support/nimbus-fml/fixtures/fe/pref_overrides.fml.yaml

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,6 @@ impl From<PropDef> for FieldBody {
impl From<PropDef> for FeatureFieldBody {
fn from(value: PropDef) -> Self {
Self {
pref_key: value.pref_key.clone(),
gecko_pref: value.gecko_pref.clone(),
string_alias: value.string_alias.as_ref().map(TypeRef::to_string),
field: value.into(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,16 +86,6 @@ impl CodeType for TextCodeType {
}
}

fn preference_getter(
&self,
oracle: &dyn CodeOracle,
prefs: &dyn Display,
pref_key: &dyn Display,
) -> Option<String> {
let ct = oracle.find(&TypeRef::String);
ct.preference_getter(oracle, prefs, pref_key)
}

fn is_resource_id(&self, literal: &Literal) -> bool {
match literal {
serde_json::Value::String(v) => is_resource_id(v),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,21 +49,6 @@ pub fn property(
Ok(ct.property_getter(oracle, &vars, &prop, &default))
}

pub fn preference_getter(
type_: impl Borrow<TypeIdentifier>,
_: &dyn askama::Values,
prefs: impl fmt::Display,
pref_key: impl fmt::Display,
) -> Result<String, askama::Error> {
let oracle = &ConcreteCodeOracle;
let ct = oracle.find(type_.borrow());
if let Some(getter) = ct.preference_getter(oracle, &prefs, &pref_key) {
Ok(getter)
} else {
unreachable!("The preference for type {} isn't available. This is a bug in Nimbus FML Kotlin generator", type_.borrow());
}
}

pub fn to_json(
prop: impl fmt::Display,
_: &dyn askama::Values,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,6 @@ impl CodeType for BooleanCodeType {
_ => unreachable!("Expecting a boolean"),
}
}

fn preference_getter(
&self,
_oracle: &dyn CodeOracle,
prefs: &dyn Display,
pref_key: &dyn Display,
) -> Option<String> {
Some(format!("{prefs}.getBoolean({}, false)", quoted(pref_key)))
}
}

pub(crate) struct IntCodeType;
Expand Down Expand Up @@ -131,15 +122,6 @@ impl CodeType for IntCodeType {
_ => unreachable!("Expecting a number"),
}
}

fn preference_getter(
&self,
_oracle: &dyn CodeOracle,
prefs: &dyn Display,
pref_key: &dyn Display,
) -> Option<String> {
Some(format!("{prefs}.getInt({}, 0)", quoted(pref_key)))
}
}

pub(crate) struct StringCodeType;
Expand Down Expand Up @@ -199,15 +181,6 @@ impl CodeType for StringCodeType {
_ => unreachable!("Expecting a string"),
}
}

fn preference_getter(
&self,
_oracle: &dyn CodeOracle,
prefs: &dyn Display,
pref_key: &dyn Display,
) -> Option<String> {
Some(format!("{prefs}.getString({}, \"\")", quoted(pref_key)))
}
}

#[cfg(test)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,4 @@
{{ inner.doc()|comment("") }}
public class {{ inner.name()|class_name }} {% call kt::render_constructor() %} : FMLFeatureInterface {
{% call kt::render_class_body(inner) %}

{%- if inner.has_prefs() %}
override fun isModified(): Boolean =
{% call kt::prefs() %}?.let { prefs ->
listOf(
{%- for p in inner.props() %}
{%- if p.has_prefs() %}
{{ p.pref_key().unwrap()|quoted }},
{%- endif %}
{%- if p.has_gecko_prefs() %}
{{ p.gecko_pref().unwrap().pref()|quoted }},
{%- endif %}
{%- endfor %}
).any { prefs.contains(it) }
} ?: false
{%- endif %}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,29 +47,6 @@ private constructor(
{%- let defaults = format!("_defaults.{}", prop_kt) %}
{%- let getter = p.typ()|property(p.name(), "_variables", defaults) %}
{{ p.doc()|comment(" ") }}
{%- if !p.has_prefs() %}
val {{ prop_kt }}: {{ type_kt }} by lazy {
{{ getter }}
}
{%- else %}
val {{ prop_kt }}: {{ type_kt }}
get() =
{%- let prefs = "it" %}
{%- let key = p.pref_key().unwrap() %}
{% call prefs() %}?.let {
if ({{ prefs }}.contains({{ key|quoted }})) {
try {
{{ p.typ()|preference_getter(prefs, key) }}
} catch (e: ClassCastException) {
// This only gets to here if the app has written the
// wrong type to this preference.
null
}
} else {
null
}
} ?: {{ getter }}
{%- endif %}
{% endfor %}

{#- toJSON #}
Expand Down
9 changes: 0 additions & 9 deletions components/support/nimbus-fml/src/backends/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,15 +145,6 @@ pub trait CodeType {
None
}

fn preference_getter(
&self,
_oracle: &dyn CodeOracle,
_prefs: &dyn Display,
_pref_key: &dyn Display,
) -> Option<String> {
None
}

/// Call from the template
fn as_json(&self, oracle: &dyn CodeOracle, prop: &dyn Display) -> String {
self.as_json_transform(oracle, prop)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,4 @@
{%- let inner = self.inner() %}
{%- let class_name = inner.name()|class_name -%}
{% call swift::render_class(inner) %}

{%- if inner.has_prefs() %}

extension {{ class_name }}: FMLFeatureInterface {
public func isModified() -> Bool {
guard let prefs = {% call swift::prefs() %} else {
return false
}
let keys = [
{%- for p in inner.props() %}
{%- if p.has_prefs() %}
{{ p.pref_key().unwrap()|quoted }},
{%- endif %}
{%- endfor %}
]
if let _ = keys.first(where: { prefs.object(forKey: $0) != nil }) {
return true
}
return false
}
}
{%- else %}
extension {{ class_name }}: FMLFeatureInterface {}
{%- endif %}
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,13 @@
{% let class_name = inner.name()|class_name -%}

{{ inner.doc()|comment("") }}
public class {{class_name}}: FMLObjectInterface {
public class {{class_name}}: FMLFeatureInterface {
private let _variables: Variables
private let _defaults: Defaults
private let _prefs: UserDefaults?

private init(variables: Variables = NilVariables.instance, prefs: UserDefaults? = nil, defaults: Defaults) {
private init(variables: Variables = NilVariables.instance, defaults: Defaults) {
self._variables = variables
self._defaults = defaults
self._prefs = prefs
}
{# The struct holds the default values that come from the manifest. They should completely
specify all values needed for the feature #}
Expand Down Expand Up @@ -70,33 +68,10 @@ public class {{class_name}}: FMLObjectInterface {
{%- let defaults = format!("_defaults.{}", prop_swift) %}
{%- let getter = p.typ()|property(p.name(), "self._variables", defaults) %}
{{ p.doc()|comment(" ") }}
{%- if !p.has_prefs() %}
public lazy var {{ prop_swift }}: {{ type_swift }} = {
{{ getter }}
}()
{%- else %}
public var {{ prop_swift }}: {{ type_swift }} {
{%- let prefs = "prefs" %}
{%- let key = p.pref_key().unwrap() %}
{#- Using `object(forKey:)` here as it returns an optional that just needs to be cast.
`integer(forKey:)` returns zero, `bool(forKey:)` returns `false` if the key is not
present.

Now we only need to use the type label as part of the cast, we don't need to implement
separate type specific preference getters.

`has_prefs()` checks if the type can be got from UserDefaults.
#}
if let {{ prefs }} = {% call prefs() %},
let {{ prop_swift }} = {{ prefs }}.object(forKey: {{ key|quoted }}) as? {{ type_swift }} {
return {{ prop_swift }}
}
return {{ getter }}
}
{%- endif %}
{% endfor %}
}

{%- endmacro %}}

{% macro prefs() %}self._prefs{% endmacro %}
12 changes: 4 additions & 8 deletions components/support/nimbus-fml/src/fixtures/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ impl PropDef {
typ: typ.clone(),
default: value.clone(),
doc: format!("{nm} property of type {typ}"),
pref_key: None,
gecko_pref: None,
string_alias: None,
}
Expand All @@ -64,18 +63,17 @@ impl PropDef {
nm: &str,
typ: &TypeRef,
value: &Value,
pref_key: &str,
pref_branch: PrefBranch,
pref: &str,
branch: PrefBranch
) -> Self {
Self {
name: nm.to_string(),
typ: typ.clone(),
default: value.clone(),
doc: format!("{nm} property of type {typ}"),
pref_key: None,
gecko_pref: Some(GeckoPrefDef {
pref: pref_key.into(),
branch: pref_branch,
pref: pref.into(),
branch: branch,
}),
string_alias: None,
}
Expand All @@ -87,7 +85,6 @@ impl PropDef {
typ: typ.clone(),
default: value.clone(),
doc: nm.to_string(),
pref_key: None,
gecko_pref: None,
string_alias: Some(sa.clone()),
}
Expand All @@ -99,7 +96,6 @@ impl PropDef {
doc: doc.to_string(),
typ: typ.clone(),
default: default.clone(),
pref_key: None,
gecko_pref: None,
string_alias: None,
}
Expand Down
Loading