Bug 1699701 - Convert all usages of ExperimentFeature to singleton API r=k88hudson

Differential Revision: https://phabricator.services.mozilla.com/D110284
This commit is contained in:
Andrei Oprea 2021-04-09 14:13:13 +00:00
Родитель a208990a84
Коммит ba84870a14
17 изменённых файлов: 53 добавлений и 271 удалений

Просмотреть файл

@ -19,7 +19,7 @@ const { PrivateBrowsingUtils } = ChromeUtils.import(
);
XPCOMUtils.defineLazyModuleGetters(this, {
ExperimentFeature: "resource://nimbus/ExperimentAPI.jsm",
NimbusFeatures: "resource://nimbus/ExperimentAPI.jsm",
});
XPCOMUtils.defineLazyPreferenceGetter(
@ -29,23 +29,13 @@ XPCOMUtils.defineLazyPreferenceGetter(
false
);
XPCOMUtils.defineLazyGetter(this, "awExperimentFeature", () => {
return new ExperimentFeature("aboutwelcome");
});
// Note: newtab feature info is currently being loaded in PrefsFeed.jsm,
// But we're recording exposure events here.
XPCOMUtils.defineLazyGetter(this, "newtabExperimentFeature", () => {
return new ExperimentFeature("newtab");
});
class AboutNewTabChild extends JSWindowActorChild {
handleEvent(event) {
if (event.type == "DOMContentLoaded") {
// If the separate about:welcome page is enabled, we can skip all of this,
// since that mode doesn't load any of the Activity Stream bits.
if (
awExperimentFeature.isEnabled({ defaultValue: true }) &&
NimbusFeatures.aboutwelcome.isEnabled({ defaultValue: true }) &&
this.contentWindow.location.pathname.includes("welcome")
) {
return;
@ -91,8 +81,9 @@ class AboutNewTabChild extends JSWindowActorChild {
) {
this.sendAsyncMessage("DefaultBrowserNotification");
// Send an exposure event to record when we have an experiment active
newtabExperimentFeature.recordExposureEvent();
// Note: newtab feature info is currently being loaded in PrefsFeed.jsm,
// But we're recording exposure events here.
NimbusFeatures.newtab.recordExposureEvent();
}
}
}

Просмотреть файл

@ -38,6 +38,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
DownloadsCommon: "resource:///modules/DownloadsCommon.jsm",
DownloadUtils: "resource://gre/modules/DownloadUtils.jsm",
E10SUtils: "resource://gre/modules/E10SUtils.jsm",
NimbusFeatures: "resource://nimbus/ExperimentAPI.jsm",
ExtensionsUI: "resource:///modules/ExtensionsUI.jsm",
HomePage: "resource:///modules/HomePage.jsm",
LightweightThemeConsumer:
@ -613,15 +614,6 @@ XPCOMUtils.defineLazyPreferenceGetter(
false
);
/* Import aboutWelcomeFeature from Nimbus Experiment API
to access experiment values */
XPCOMUtils.defineLazyGetter(this, "aboutWelcomeFeature", () => {
const { ExperimentFeature } = ChromeUtils.import(
"resource://nimbus/ExperimentAPI.jsm"
);
return new ExperimentFeature("aboutwelcome");
});
customElements.setElementCreationCallback("translation-notification", () => {
Services.scriptloader.loadSubScript(
"chrome://browser/content/translation-notification.js",
@ -2232,7 +2224,7 @@ var gBrowserInit = {
// property set to true, if yes remove focus from urlbar for about:welcome
const aboutWelcomeSkipUrlBarFocus =
uriToLoad == "about:welcome" &&
aboutWelcomeFeature.getValue()?.skipFocus;
NimbusFeatures.aboutwelcome.getValue()?.skipFocus;
if (
(isBlankPageURL(uriToLoad) && !aboutWelcomeSkipUrlBarFocus) ||

Просмотреть файл

@ -41,11 +41,8 @@ const { E10SUtils } = ChromeUtils.import(
"resource://gre/modules/E10SUtils.jsm"
);
XPCOMUtils.defineLazyGetter(this, "awExperimentFeature", () => {
const { ExperimentFeature } = ChromeUtils.import(
"resource://nimbus/ExperimentAPI.jsm"
);
return new ExperimentFeature("aboutwelcome");
XPCOMUtils.defineLazyModuleGetters(this, {
NimbusFeatures: "resource://nimbus/ExperimentAPI.jsm",
});
/**
@ -449,7 +446,7 @@ class BaseAboutNewTabService {
*/
if (
awExperimentFeature.isEnabled({
NimbusFeatures.aboutwelcome.isEnabled({
defaultValue: true,
sendExposureEvent: true,
})

Просмотреть файл

@ -17,6 +17,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
TippyTopProvider: "resource://activity-stream/lib/TippyTopProvider.jsm",
AboutWelcomeDefaults:
"resource://activity-stream/aboutwelcome/lib/AboutWelcomeDefaults.jsm",
NimbusFeatures: "resource://nimbus/ExperimentAPI.jsm",
});
XPCOMUtils.defineLazyGetter(this, "log", () => {
@ -26,13 +27,6 @@ XPCOMUtils.defineLazyGetter(this, "log", () => {
return new Logger("AboutWelcomeChild");
});
XPCOMUtils.defineLazyGetter(this, "aboutWelcomeFeature", () => {
const { ExperimentFeature } = ChromeUtils.import(
"resource://nimbus/ExperimentAPI.jsm"
);
return new ExperimentFeature("aboutwelcome");
});
XPCOMUtils.defineLazyGetter(this, "tippyTopProvider", () =>
(async () => {
const provider = new TippyTopProvider();
@ -222,7 +216,7 @@ class AboutWelcomeChild extends JSWindowActorChild {
ExperimentAPI.getExperimentMetaData({
featureId: "aboutwelcome",
}) || {};
let featureConfig = aboutWelcomeFeature.getValue() || {};
let featureConfig = NimbusFeatures.aboutwelcome.getValue() || {};
if (experimentMetadata?.slug) {
log.debug(

Просмотреть файл

@ -14,11 +14,8 @@ const { actionTypes: at, actionCreators: ac } = ChromeUtils.import(
const HTML_NS = "http://www.w3.org/1999/xhtml";
const PREFERENCES_LOADED_EVENT = "home-pane-loaded";
XPCOMUtils.defineLazyGetter(this, "aboutNewTabFeature", () => {
const { ExperimentFeature } = ChromeUtils.import(
"resource://nimbus/ExperimentAPI.jsm"
);
return new ExperimentFeature("newtab");
XPCOMUtils.defineLazyModuleGetters(this, {
NimbusFeatures: "resource://nimbus/ExperimentAPI.jsm",
});
// These "section" objects are formatted in a way to be similar to the ones from
@ -151,7 +148,7 @@ this.AboutPreferences = class AboutPreferences {
sections = this.handleDiscoverySettings(sections);
}
const featureConfig = aboutNewTabFeature.getValue() || {};
const featureConfig = NimbusFeatures.newtab.getValue() || {};
this.renderPreferences(window, [
...PREFS_BEFORE_SECTIONS(featureConfig),

Просмотреть файл

@ -14,31 +14,13 @@ const { Prefs } = ChromeUtils.import(
);
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.defineModuleGetter(
this,
"PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"AppConstants",
"resource://gre/modules/AppConstants.jsm"
);
XPCOMUtils.defineLazyGetter(this, "aboutNewTabFeature", () => {
const { ExperimentFeature } = ChromeUtils.import(
"resource://nimbus/ExperimentAPI.jsm"
);
return new ExperimentFeature("newtab");
XPCOMUtils.defineLazyModuleGetters(this, {
AppConstants: "resource://gre/modules/AppConstants.jsm",
NimbusFeatures: "resource://nimbus/ExperimentAPI.jsm",
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
Region: "resource://gre/modules/Region.jsm",
});
ChromeUtils.defineModuleGetter(
this,
"Region",
"resource://gre/modules/Region.jsm"
);
this.PrefsFeed = class PrefsFeed {
constructor(prefMap) {
this._prefMap = prefMap;
@ -83,7 +65,7 @@ this.PrefsFeed = class PrefsFeed {
* Handler for when experiment data updates.
*/
onExperimentUpdated(event, reason) {
const value = aboutNewTabFeature.getValue() || {};
const value = NimbusFeatures.newtab.getValue() || {};
this.store.dispatch(
ac.BroadcastToContent({
type: at.PREF_CHANGED,
@ -97,7 +79,7 @@ this.PrefsFeed = class PrefsFeed {
init() {
this._prefs.observeBranch(this);
aboutNewTabFeature.onUpdate(this.onExperimentUpdated);
NimbusFeatures.newtab.onUpdate(this.onExperimentUpdated);
this._storage = this.store.dbStorage.getDbTable("sectionPrefs");
@ -172,7 +154,7 @@ this.PrefsFeed = class PrefsFeed {
});
// Add experiment values and default values
values.featureConfig = aboutNewTabFeature.getValue() || {};
values.featureConfig = NimbusFeatures.newtab.getValue() || {};
this._setBoolPref(values, "logowordmark.alwaysVisible", false);
this._setBoolPref(values, "feeds.section.topstories", false);
this._setBoolPref(values, "discoverystream.enabled", false);
@ -209,7 +191,7 @@ this.PrefsFeed = class PrefsFeed {
removeListeners() {
this._prefs.ignoreBranch(this);
aboutNewTabFeature.off(this.onExperimentUpdated);
NimbusFeatures.newtab.off(this.onExperimentUpdated);
if (this.geo === "") {
Services.obs.removeObserver(this, Region.REGION_TOPIC);
}

Просмотреть файл

@ -17,18 +17,9 @@ const { getDefaultOptions } = ChromeUtils.import(
"resource://activity-stream/lib/ActivityStreamStorage.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm"
);
XPCOMUtils.defineLazyGetter(this, "aboutNewTabFeature", () => {
const { ExperimentFeature } = ChromeUtils.import(
"resource://nimbus/ExperimentAPI.jsm"
);
return new ExperimentFeature("newtab");
XPCOMUtils.defineLazyModuleGetters(this, {
NimbusFeatures: "resource://nimbus/ExperimentAPI.jsm",
PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
});
/*
@ -214,7 +205,7 @@ const SectionsManager = {
sections: new Map(),
async init(prefs = {}, storage) {
this._storage = storage;
const featureConfig = aboutNewTabFeature.getValue() || {};
const featureConfig = NimbusFeatures.newtab.getValue() || {};
for (const feedPrefName of Object.keys(BUILT_IN_SECTIONS(featureConfig))) {
const optionsPrefName = `${feedPrefName}.options`;
@ -263,7 +254,7 @@ const SectionsManager = {
async addBuiltInSection(feedPrefName, optionsPrefValue = "{}") {
let options;
let storedPrefs;
const featureConfig = aboutNewTabFeature.getValue() || {};
const featureConfig = NimbusFeatures.newtab.getValue() || {};
try {
options = JSON.parse(optionsPrefValue);
} catch (e) {

Просмотреть файл

@ -23,7 +23,7 @@ describe("AboutPreferences Feed", () => {
dispatch: sandbox.stub(),
getState: () => ({ Sections, DiscoveryStream }),
};
globals.set("aboutNewTabFeature", { getValue: sandbox.stub() });
globals.set("NimbusFeatures", { newtab: { getValue: sandbox.stub() } });
});
afterEach(() => {
globals.restore();

Просмотреть файл

@ -55,7 +55,7 @@ describe("PrefsFeed", () => {
overrider.set({
PrivateBrowsingUtils: { enabled: true },
Services: ServicesStub,
aboutNewTabFeature: new global.ExperimentFeature(),
NimbusFeatures: { newtab: new global.ExperimentFeature() },
});
});
afterEach(() => {
@ -84,7 +84,7 @@ describe("PrefsFeed", () => {
assert.isTrue(data.isPrivateBrowsingEnabled);
});
it("should dispatch PREFS_INITIAL_VALUES with a .featureConfig", () => {
sandbox.stub(global.aboutNewTabFeature, "getValue").returns({
sandbox.stub(global.NimbusFeatures.newtab, "getValue").returns({
prefsButtonIcon: "icon-foo",
});
feed.onAction({ type: at.INIT });
@ -96,7 +96,7 @@ describe("PrefsFeed", () => {
assert.deepEqual(data.featureConfig, { prefsButtonIcon: "icon-foo" });
});
it("should dispatch PREFS_INITIAL_VALUES with an empty object if no experiment is returned", () => {
sandbox.stub(global.aboutNewTabFeature, "getValue").returns(null);
sandbox.stub(global.NimbusFeatures.newtab, "getValue").returns(null);
feed.onAction({ type: at.INIT });
assert.equal(
feed.store.dispatch.firstCall.args[0].type,
@ -155,7 +155,7 @@ describe("PrefsFeed", () => {
);
});
it("should send a PREF_CHANGED actions when onExperimentUpdated is called", () => {
sandbox.stub(global.aboutNewTabFeature, "getValue").returns({
sandbox.stub(global.NimbusFeatures.newtab, "getValue").returns({
prefsButtonIcon: "icon-new",
});
feed.onExperimentUpdated();

Просмотреть файл

@ -43,7 +43,7 @@ describe("SectionsManager", () => {
globals.set({
Services: fakeServices,
PlacesUtils: fakePlacesUtils,
aboutNewTabFeature: { getValue: sandbox.stub() },
NimbusFeatures: { newtab: { getValue: sandbox.stub() } },
});
// Redecorate SectionsManager to remove any listeners that have been added
EventEmitter.decorate(SectionsManager);
@ -521,7 +521,7 @@ describe("SectionsFeed", () => {
SectionsManager.sections.clear();
SectionsManager.initialized = false;
globals = new GlobalOverrider();
globals.set("aboutNewTabFeature", { getValue: sandbox.stub() });
globals.set("NimbusFeatures", { newtab: { getValue: sandbox.stub() } });
storage = {
get: sandbox.stub().resolves(),
set: sandbox.stub().resolves(),

Просмотреть файл

@ -27,7 +27,7 @@ ChromeUtils.defineModuleGetter(
);
ChromeUtils.defineModuleGetter(
this,
"ExperimentFeature",
"NimbusFeatures",
"resource://nimbus/ExperimentAPI.jsm"
);
@ -134,10 +134,9 @@ var gSearchPane = {
this._updateSuggestionCheckboxes();
this._showAddEngineButton();
this._updateQuickSuggest = this._updateQuickSuggest.bind(this);
this.urlbarExperimentFeature = new ExperimentFeature("urlbar");
this.urlbarExperimentFeature.onUpdate(this._updateQuickSuggest);
NimbusFeatures.urlbar.onUpdate(this._updateQuickSuggest);
window.addEventListener("unload", () => {
this.urlbarExperimentFeature.off(this._updateQuickSuggest);
NimbusFeatures.urlbar.off(this._updateQuickSuggest);
});
this._updateQuickSuggest(true);
},
@ -249,7 +248,7 @@ var gSearchPane = {
let container = document.getElementById("showQuickSuggestContainer");
let desc = document.getElementById("searchSuggestionsDesc");
if (!this.urlbarExperimentFeature.getValue().quickSuggestEnabled) {
if (!NimbusFeatures.urlbar.getValue().quickSuggestEnabled) {
// The experiment is not enabled. This is the default, so to avoid
// accidentally messing anything up, only modify the doc if we're being
// called due to a change in the experiment enabled status.

Просмотреть файл

@ -13,6 +13,7 @@ const { XPCOMUtils } = ChromeUtils.import(
XPCOMUtils.defineLazyModuleGetters(this, {
CONTEXTUAL_SERVICES_PING_TYPES:
"resource:///modules/PartnerLinkAttribution.jsm",
NimbusFeatures: "resource://nimbus/ExperimentAPI.jsm",
PartnerLinkAttribution: "resource:///modules/PartnerLinkAttribution.jsm",
Services: "resource://gre/modules/Services.jsm",
UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
@ -22,13 +23,6 @@ XPCOMUtils.defineLazyModuleGetters(this, {
UrlbarUtils: "resource:///modules/UrlbarUtils.jsm",
});
XPCOMUtils.defineLazyGetter(this, "urlbarExperimentFeature", () => {
const { ExperimentFeature } = ChromeUtils.import(
"resource://nimbus/ExperimentAPI.jsm"
);
return new ExperimentFeature("urlbar");
});
// These prefs are relative to the `browser.urlbar` branch.
const SUGGEST_PREF = "suggest.quicksuggest";
@ -51,7 +45,7 @@ class ProviderQuickSuggest extends UrlbarProvider {
super(...args);
this._updateExperimentState();
UrlbarPrefs.addObserver(this);
urlbarExperimentFeature.onUpdate(this._updateExperimentState);
NimbusFeatures.urlbar.onUpdate(this._updateExperimentState);
}
/**
@ -102,7 +96,7 @@ class ProviderQuickSuggest extends UrlbarProvider {
return (
queryContext.trimmedSearchString &&
!queryContext.searchMode &&
urlbarExperimentFeature.getValue().quickSuggestEnabled &&
NimbusFeatures.urlbar.getValue().quickSuggestEnabled &&
UrlbarPrefs.get(SUGGEST_PREF) &&
UrlbarPrefs.get("suggest.searches") &&
UrlbarPrefs.get("browser.search.suggest.enabled") &&
@ -283,12 +277,12 @@ class ProviderQuickSuggest extends UrlbarProvider {
_updateExperimentState() {
Services.telemetry.setEventRecordingEnabled(
TELEMETRY_EVENT_CATEGORY,
urlbarExperimentFeature.getValue().quickSuggestEnabled
NimbusFeatures.urlbar.getValue().quickSuggestEnabled
);
// QuickSuggest is only loaded by the UrlBar on it's first query, however
// there is work it can preload when idle instead of starting it on user
// input. Referencing it here will trigger its import and init.
if (urlbarExperimentFeature.getValue().quickSuggestEnabled) {
if (NimbusFeatures.urlbar.getValue().quickSuggestEnabled) {
UrlbarQuickSuggest; // eslint-disable-line no-unused-expressions
}
}

Просмотреть файл

@ -15,7 +15,7 @@ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetters(this, {
RemoteSettings: "resource://services-settings/remote-settings.js",
UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
ExperimentFeature: "resource://nimbus/ExperimentAPI.jsm",
NimbusFeatures: "resource://nimbus/ExperimentAPI.jsm",
});
XPCOMUtils.defineLazyGlobalGetters(this, ["TextDecoder"]);
@ -53,14 +53,13 @@ class Suggestions {
this._initPromise = Promise.resolve();
this._rs = RemoteSettings(RS_COLLECTION);
this.onFeatureUpdate = this.onFeatureUpdate.bind(this);
this.urlbarExperimentFeature = new ExperimentFeature("urlbar");
if (this.urlbarExperimentFeature.getValue().quickSuggestEnabled) {
if (NimbusFeatures.urlbar.getValue().quickSuggestEnabled) {
this._initPromise = new Promise(resolve => (this._initResolve = resolve));
Services.tm.idleDispatchToMainThread(
this._setupRemoteSettings.bind(this)
);
} else {
this.urlbarExperimentFeature.onUpdate(this.onFeatureUpdate);
NimbusFeatures.urlbar.onUpdate(this.onFeatureUpdate);
}
return this._initPromise;
}
@ -155,7 +154,7 @@ class Suggestions {
* Called if a Urlbar Experiment Feature is changed.
*/
onFeatureUpdate() {
if (this.urlbarExperimentFeature.getValue().quickSuggestEnabled) {
if (NimbusFeatures.urlbar.getValue().quickSuggestEnabled) {
this._setupRemoteSettings();
}
}

Просмотреть файл

@ -5,12 +5,4 @@ About
------
Nimbus is an automated experimentation system.
Learn more
---------------------
.. toctree::
:maxdepth: 2
integration.md
testing.md
Our API docs are hosted with the `Experimenter Docs <https://mozilla.github.io/experimenter-docs/desktop-feature-api>`_.

Просмотреть файл

@ -1,80 +0,0 @@
# Nimbus Integration and Migration Guide for Desktop Front-End Experiments
This guide will help you integrate `ExperimentAPI.jsm` in your Desktop front-end code to run Nimbus experiments, while still being able to use preferences for default values and local development.
In order to use `ExperimentAPI.jsm` your code must be able to import `jsm`s in the parent process or a privileged child process.
## Register a new feature
A feature is just some area of code instrumented for experiments – it can be as small as a single function or as complex as a whole about: page. You need to choose an identifier for your feature (e.g. "aboutnewtab").
```javascript
// In ExperimentAPI.jsm
const MANIFEST = {
// Our feature name
aboutwelcome: {
description: "The about:welcome page",
// Control if the feature is on or off
enabledFallbackPref: "browser.aboutwelcome.enabled",
variables: {
// Additional (optional) values that we can control
// The name of these variables is up to you
skipFocus: {
type: "boolean",
fallbackPref: "browser.aboutwelcome.skipFocus",
},
},
},
};
// In firefox.js
pref("browser.aboutwelcome.enable", true);
pref("skipFocus", false);
```
> By setting fallback preferences for Nimbus features, you will be able to still run Normandy roll-outs and experiments while you are partially migrated. We do not recommend running Nimbus and Normandy experiments on the same feature/preference simultaneously.
## How to use an Experiment Feature
Import `ExperimentFeature` from `ExperimentAPI.jsm` and instantiate an instance
```jsx
XPCOMUtils.defineLazyGetter(this, "feature", () => {
const { ExperimentFeature } = ChromeUtils.import(
"resource://nimbus/ExperimentAPI.jsm"
);
// Here we use the same name we defined in the MANIFEST
return new ExperimentFeature("aboutwelcome");
});
```
Access feature values:
```jsx
if (feature.isEnabled() {
// Do something!
// This is controllbed by the `enabledFallbackPref` defined in the MANIFEST
}
// props: { skipFocus: boolean }
const props = feature.getValue();
renderSomeUI(props);
```
Defaults values inline:
```jsx
feature.isEnabled({ defaultValue: true });
const { skipFocus } = feature.getValue() || {};
```
Listen to changes:
```jsx
// Listen to changes, including to fallback prefs.
feature.on(() => {
updateUI(feature.getValue());
});
```

Просмотреть файл

@ -1,61 +0,0 @@
# Nimbus Testing Helpers
In order to make testing easier we created some helpers that can be accessed by including
```js
const { ExperimentFakes } = ChromeUtils.import(
"resource://testing-common/NimbusTestUtils.jsm"
);
```
## Testing your feature integrating with Nimbus
1. You need to create a recipe
```js
let recipe = ExperimentFakes.recipe("my-cool-experiment", {
branches: [
{
slug: "treatment-branch",
ratio: 1,
feature: {
featureId: "<YOUR FEATURE>",
// The feature is on
enabled: true,
// If you defined `variables` in the MANIFEST
// the `value` should match that schema
value: null,
},
},
],
bucketConfig: {
start: 0,
// Ensure 100% enrollment
count: 10000,
total: 10000,
namespace: "my-mochitest",
randomizationUnit: "normandy_id",
},
});
```
2. Now with the newly created recipe you want the test to enroll in the experiment
```js
let {
enrollmentPromise,
doExperimentCleanup,
} = ExperimentFakes.enrollmentHelper(recipe);
// Await for enrollment to complete
await enrollmentPromise;
// Now you can assume the feature is enabled so you can
// test and that it's doing the right thing
// Assert.ok(It works!)
// Finishing up
await doExperimentCleanup();
```

Просмотреть файл

@ -15,13 +15,6 @@ const LoginInfo = new Components.Constructor(
"init"
);
XPCOMUtils.defineLazyGetter(this, "autocompleteFeature", () => {
const { ExperimentFeature } = ChromeUtils.import(
"resource://nimbus/ExperimentAPI.jsm"
);
return new ExperimentFeature("password-autocomplete");
});
XPCOMUtils.defineLazyGetter(this, "LoginRelatedRealmsParent", () => {
const { LoginRelatedRealmsParent } = ChromeUtils.import(
"resource://gre/modules/LoginRelatedRealms.jsm"
@ -33,6 +26,7 @@ XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
XPCOMUtils.defineLazyModuleGetters(this, {
ChromeMigrationUtils: "resource:///modules/ChromeMigrationUtils.jsm",
NimbusFeatures: "resource://nimbus/ExperimentAPI.jsm",
LoginHelper: "resource://gre/modules/LoginHelper.jsm",
MigrationUtils: "resource:///modules/MigrationUtils.jsm",
PasswordGenerator: "resource://gre/modules/PasswordGenerator.jsm",
@ -341,7 +335,8 @@ class LoginManagerParent extends JSWindowActorParent {
const profiles = await migrator.getSourceProfiles();
if (
profiles.length == 1 &&
autocompleteFeature.getValue()?.directMigrateSingleProfile
NimbusFeatures["password-autocomplete"].getValue()
?.directMigrateSingleProfile
) {
const loginAdded = new Promise(resolve => {
const obs = (subject, topic, data) => {