Bug 1694257 - Add ability for targeted defaults for AboutWelcome r=k88hudson
authorAndrei Oprea <andrei.br92@gmail.com>
Tue, 16 Mar 2021 17:49:41 +0000
changeset 571454 96d3adba86e67b8c25fee69b3da2da8ed9db49fd
parent 571453 7cb14dd00756918e666093c3f731b0d4515c26ad
child 571455 2f40d9c4e5f58bdf71f7100734b1677af3065e59
push id38293
push usermalexandru@mozilla.com
push dateWed, 17 Mar 2021 09:53:31 +0000
treeherdermozilla-central@9ad67cd4d216 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersk88hudson
bugs1694257
milestone88.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1694257 - Add ability for targeted defaults for AboutWelcome r=k88hudson Differential Revision: https://phabricator.services.mozilla.com/D106418
browser/components/newtab/aboutwelcome/AboutWelcomeChild.jsm
browser/components/newtab/aboutwelcome/AboutWelcomeParent.jsm
browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js
browser/components/newtab/aboutwelcome/lib/AboutWelcomeDefaults.jsm
browser/components/newtab/content-src/aboutwelcome/aboutwelcome.jsx
browser/components/newtab/content-src/lib/aboutwelcome-utils.js
browser/components/newtab/test/browser/browser_aboutwelcome_multistage.js
browser/components/newtab/test/unit/aboutwelcome/MultiStageAboutWelcome.test.jsx
browser/components/newtab/test/xpcshell/test_AboutWelcomeAttribution.js
toolkit/components/nimbus/ExperimentAPI.jsm
toolkit/components/nimbus/docs/integration.md
toolkit/components/nimbus/test/unit/test_ExperimentAPI_ExperimentFeature.js
toolkit/components/nimbus/test/unit/test_ExperimentAPI_ExperimentFeature_getValue.js
toolkit/components/nimbus/test/unit/xpcshell.ini
--- a/browser/components/newtab/aboutwelcome/AboutWelcomeChild.jsm
+++ b/browser/components/newtab/aboutwelcome/AboutWelcomeChild.jsm
@@ -10,16 +10,18 @@ const { XPCOMUtils } = ChromeUtils.impor
   "resource://gre/modules/XPCOMUtils.jsm"
 );
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   DEFAULT_SITES: "resource://activity-stream/lib/DefaultSites.jsm",
   ExperimentAPI: "resource://nimbus/ExperimentAPI.jsm",
   shortURL: "resource://activity-stream/lib/ShortURL.jsm",
   TippyTopProvider: "resource://activity-stream/lib/TippyTopProvider.jsm",
+  AboutWelcomeDefaults:
+    "resource://activity-stream/aboutwelcome/lib/AboutWelcomeDefaults.jsm",
 });
 
 XPCOMUtils.defineLazyGetter(this, "log", () => {
   const { Logger } = ChromeUtils.import(
     "resource://messaging-system/lib/Logger.jsm"
   );
   return new Logger("AboutWelcomeChild");
 });
@@ -151,20 +153,16 @@ class AboutWelcomeChild extends JSWindow
    */
   exportFunctions() {
     let window = this.contentWindow;
 
     Cu.exportFunction(this.AWGetFeatureConfig.bind(this), window, {
       defineAs: "AWGetFeatureConfig",
     });
 
-    Cu.exportFunction(this.AWGetAttributionData.bind(this), window, {
-      defineAs: "AWGetAttributionData",
-    });
-
     Cu.exportFunction(this.AWGetFxAMetricsFlowURI.bind(this), window, {
       defineAs: "AWGetFxAMetricsFlowURI",
     });
 
     Cu.exportFunction(this.AWGetImportableSites.bind(this), window, {
       defineAs: "AWGetImportableSites",
     });
 
@@ -211,108 +209,61 @@ class AboutWelcomeChild extends JSWindow
   }
 
   AWSelectTheme(data) {
     return this.wrapPromise(
       this.sendQuery("AWPage:SELECT_THEME", data.toUpperCase())
     );
   }
 
-  async getAddonInfo(attrbObj) {
-    let { content, source } = attrbObj;
-    try {
-      if (!content || source !== "addons.mozilla.org") {
-        return null;
-      }
-      // Attribution data can be double encoded
-      while (content.includes("%")) {
-        try {
-          const result = decodeURIComponent(content);
-          if (result === content) {
-            break;
-          }
-          content = result;
-        } catch (e) {
-          break;
-        }
-      }
-      return await this.sendQuery("AWPage:GET_ADDON_FROM_REPOSITORY", content);
-    } catch (e) {
-      Cu.reportError(
-        "Failed to get the latest add-on version for Return to AMO"
-      );
-      return null;
-    }
-  }
-
-  hasAMOAttribution(attributionData) {
-    return (
-      attributionData &&
-      attributionData.campaign === "non-fx-button" &&
-      attributionData.source === "addons.mozilla.org"
-    );
-  }
-
-  async formatAttributionData(attribution) {
-    let result = {};
-    if (this.hasAMOAttribution(attribution)) {
-      let extraProps = await this.getAddonInfo(attribution);
-      if (extraProps) {
-        result = {
-          template: "return_to_amo",
-          extraProps,
-        };
-      }
-    }
-    return result;
-  }
-
-  async getAttributionData() {
-    return Cu.cloneInto(
-      await this.formatAttributionData(
-        await this.sendQuery("AWPage:GET_ATTRIBUTION_DATA")
-      ),
-      this.contentWindow
-    );
-  }
-
-  AWGetAttributionData() {
-    return this.wrapPromise(this.getAttributionData());
-  }
-
   /**
    * Send initial data to page including experiment information
    */
-  AWGetFeatureConfig() {
-    // Note that we specifically don't wait for `ready` so if
-    // about:welcome loads outside of the "FirstStartup" scenario this will likely not be ready
+  async getAWContent() {
     let experimentMetadata =
       ExperimentAPI.getExperimentMetaData({
         featureId: "aboutwelcome",
       }) || {};
-    let featureConfig = aboutWelcomeFeature.getValue({ defaultValue: {} });
+    let featureConfig = aboutWelcomeFeature.getValue() || {};
 
     if (experimentMetadata?.slug) {
       log.debug(
         `Loading about:welcome with experiment: ${experimentMetadata.slug}`
       );
     } else {
       log.debug("Loading about:welcome without experiment");
+      let attributionData = await this.sendQuery("AWPage:GET_ATTRIBUTION_DATA");
+      if (attributionData) {
+        log.debug("Loading about:welcome with attribution data");
+        featureConfig = { ...attributionData, ...featureConfig };
+      } else {
+        log.debug("Loading about:welcome with default data");
+        let defaults = AboutWelcomeDefaults.getDefaults();
+        // FeatureConfig (from prefs or experiments) has higher precendence
+        // to defaults. But the `screens` property isn't defined we shouldn't
+        // override the default with `null`
+        let screens = featureConfig.screens || defaults.screens;
+        featureConfig = {
+          ...defaults,
+          ...featureConfig,
+          screens,
+        };
+      }
     }
+
     return Cu.cloneInto(
-      {
-        // All experimentation right now is using the multistage template
-        template: "multistage",
-        ...experimentMetadata,
-        ...featureConfig,
-      },
+      { ...experimentMetadata, ...featureConfig },
       this.contentWindow
     );
   }
 
+  AWGetFeatureConfig() {
+    return this.wrapPromise(this.getAWContent());
+  }
+
   AWGetFxAMetricsFlowURI() {
     return this.wrapPromise(this.sendQuery("AWPage:FXA_METRICS_FLOW_URI"));
   }
 
   AWGetImportableSites() {
     return this.wrapPromise(getImportableSites(this));
   }
 
--- a/browser/components/newtab/aboutwelcome/AboutWelcomeParent.jsm
+++ b/browser/components/newtab/aboutwelcome/AboutWelcomeParent.jsm
@@ -8,25 +8,25 @@ const EXPORTED_SYMBOLS = ["AboutWelcomeP
 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 const { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
 );
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   AddonManager: "resource://gre/modules/AddonManager.jsm",
-  AddonRepository: "resource://gre/modules/addons/AddonRepository.jsm",
   FxAccounts: "resource://gre/modules/FxAccounts.jsm",
   MigrationUtils: "resource:///modules/MigrationUtils.jsm",
   OS: "resource://gre/modules/osfile.jsm",
   SpecialMessageActions:
     "resource://messaging-system/lib/SpecialMessageActions.jsm",
   AboutWelcomeTelemetry:
     "resource://activity-stream/aboutwelcome/lib/AboutWelcomeTelemetry.jsm",
-  AttributionCode: "resource:///modules/AttributionCode.jsm",
+  AboutWelcomeDefaults:
+    "resource://activity-stream/aboutwelcome/lib/AboutWelcomeDefaults.jsm",
   PromiseUtils: "resource://gre/modules/PromiseUtils.jsm",
   Region: "resource://gre/modules/Region.jsm",
 });
 
 XPCOMUtils.defineLazyGetter(this, "log", () => {
   const { Logger } = ChromeUtils.import(
     "resource://messaging-system/lib/Logger.jsm"
   );
@@ -219,37 +219,28 @@ class AboutWelcomeParent extends JSWindo
           log.debug(`Fails to set ${DID_SEE_ABOUT_WELCOME_PREF}.`);
         }
         break;
       case "AWPage:SPECIAL_ACTION":
         SpecialMessageActions.handleAction(data, browser);
         break;
       case "AWPage:FXA_METRICS_FLOW_URI":
         return FxAccounts.config.promiseMetricsFlowURI("aboutwelcome");
-      case "AWPage:GET_ATTRIBUTION_DATA":
-        return AttributionCode.getAttrDataAsync();
       case "AWPage:IMPORTABLE_SITES":
         return getImportableSites();
       case "AWPage:TELEMETRY_EVENT":
         Telemetry.sendTelemetry(data);
         break;
       case "AWPage:LOCATION_CHANGED":
         this.AboutWelcomeObserver.terminateReason =
           AWTerminate.ADDRESS_BAR_NAVIGATED;
         break;
-      case "AWPage:GET_ADDON_FROM_REPOSITORY":
-        const [addonInfo] = await AddonRepository.getAddonsByIDs([data]);
-        if (addonInfo.sourceURI.scheme !== "https") {
-          return null;
-        }
-        return {
-          name: addonInfo.name,
-          url: addonInfo.sourceURI.spec,
-          iconURL: addonInfo.icons["64"] || addonInfo.icons["32"],
-        };
+      case "AWPage:GET_ATTRIBUTION_DATA":
+        let attributionData = await AboutWelcomeDefaults.getAttributionContent();
+        return attributionData;
       case "AWPage:SELECT_THEME":
         return AddonManager.getAddonByID(
           LIGHT_WEIGHT_THEMES[data]
         ).then(addon => addon.enable());
       case "AWPage:GET_SELECTED_THEME":
         let themes = await AddonManager.getAddonsByTypes(["theme"]);
         let activeTheme = themes.find(addon => addon.isActive);
 
--- a/browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js
+++ b/browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js
@@ -97,28 +97,26 @@
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
 /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2);
 /* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_dom__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _components_MultiStageAboutWelcome__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(3);
 /* harmony import */ var _components_ReturnToAMO__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(9);
-/* harmony import */ var _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(7);
 function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
 
 /* 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 http://mozilla.org/MPL/2.0/. */
 
 
 
 
 
-
 class AboutWelcome extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComponent {
   constructor(props) {
     super(props);
     this.state = {
       metricsFlowUri: null
     };
     this.fetchFxAFlowUri = this.fetchFxAFlowUri.bind(this);
   }
@@ -184,19 +182,18 @@ class AboutWelcome extends react__WEBPAC
     return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_components_MultiStageAboutWelcome__WEBPACK_IMPORTED_MODULE_2__["MultiStageAboutWelcome"], {
       screens: props.screens,
       metricsFlowUri: this.state.metricsFlowUri,
       message_id: props.messageId,
       utm_term: props.UTMTerm
     });
   }
 
-}
+} // Computes messageId and UTMTerm info used in telemetry
 
-AboutWelcome.defaultProps = _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_4__["DEFAULT_WELCOME_CONTENT"]; // Computes messageId and UTMTerm info used in telemetry
 
 function ComputeTelemetryInfo(welcomeContent, experimentId, branchId) {
   let messageId = welcomeContent.template === "return_to_amo" ? "RTAMO_DEFAULT_WELCOME" : "DEFAULT_ABOUTWELCOME";
   let UTMTerm = "default";
 
   if (welcomeContent.id) {
     messageId = welcomeContent.id.toUpperCase();
   }
@@ -207,54 +204,36 @@ function ComputeTelemetryInfo(welcomeCon
 
   return {
     messageId,
     UTMTerm
   };
 }
 
 async function retrieveRenderContent() {
-  var _aboutWelcomeProps;
-
-  // Check for featureConfig and retrieve content
-  const featureConfig = await window.AWGetFeatureConfig();
-  let aboutWelcomeProps;
-
-  if (!featureConfig.screens) {
-    const attribution = await window.AWGetAttributionData();
-    aboutWelcomeProps = {
-      template: attribution.template,
-      ...attribution.extraProps
-    };
-  } else {
-    // If screens is defined then we have multi stage AW content to show
-    aboutWelcomeProps = featureConfig.screens ? featureConfig : {};
-  } // Set design if exists in featureConfig
-
-
-  if (featureConfig.design && !((_aboutWelcomeProps = aboutWelcomeProps) !== null && _aboutWelcomeProps !== void 0 && _aboutWelcomeProps.design)) {
-    aboutWelcomeProps = { ...aboutWelcomeProps,
-      design: featureConfig.design
-    };
-  }
-
+  // Feature config includes:
+  // user prefs
+  // experiment data
+  // attribution data
+  // defaults
+  let featureConfig = await window.AWGetFeatureConfig();
   let {
     messageId,
     UTMTerm
-  } = ComputeTelemetryInfo(aboutWelcomeProps, featureConfig.slug, featureConfig.branch && featureConfig.branch.slug);
+  } = ComputeTelemetryInfo(featureConfig, featureConfig.slug, featureConfig.branch && featureConfig.branch.slug);
   return {
-    aboutWelcomeProps,
+    featureConfig,
     messageId,
     UTMTerm
   };
 }
 
 async function mount() {
   let {
-    aboutWelcomeProps,
+    featureConfig: aboutWelcomeProps,
     messageId,
     UTMTerm
   } = await retrieveRenderContent();
   react_dom__WEBPACK_IMPORTED_MODULE_1___default.a.render( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(AboutWelcome, _extends({
     messageId: messageId,
     UTMTerm: UTMTerm
   }, aboutWelcomeProps)), document.getElementById("root"));
 }
@@ -875,17 +854,16 @@ const HelpText = props => {
 /***/ }),
 /* 7 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AboutWelcomeUtils", function() { return AboutWelcomeUtils; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "DEFAULT_RTAMO_CONTENT", function() { return DEFAULT_RTAMO_CONTENT; });
-/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "DEFAULT_WELCOME_CONTENT", function() { return DEFAULT_WELCOME_CONTENT; });
 /* 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 http://mozilla.org/MPL/2.0/. */
 const AboutWelcomeUtils = {
   handleUserAction(action) {
     window.AWSendToParent("SPECIAL_ACTION", action);
   },
 
@@ -980,182 +958,16 @@ const DEFAULT_RTAMO_CONTENT = {
       },
       message_id: "RTAMO_START_BROWSING_BUTTON",
       action: {
         type: "OPEN_AWESOME_BAR"
       }
     }
   }
 };
-const DEFAULT_WELCOME_CONTENT = {
-  template: "multistage",
-  screens: [{
-    id: "AW_SET_DEFAULT",
-    order: 0,
-    content: {
-      zap: true,
-      title: {
-        string_id: "onboarding-multistage-set-default-header"
-      },
-      subtitle: {
-        string_id: "onboarding-multistage-set-default-subtitle"
-      },
-      primary_button: {
-        label: {
-          string_id: "onboarding-multistage-set-default-primary-button-label"
-        },
-        action: {
-          navigate: true,
-          type: "SET_DEFAULT_BROWSER"
-        }
-      },
-      secondary_button: {
-        label: {
-          string_id: "onboarding-multistage-set-default-secondary-button-label"
-        },
-        action: {
-          navigate: true
-        }
-      },
-      secondary_button_top: {
-        text: {
-          string_id: "onboarding-multistage-welcome-secondary-button-text"
-        },
-        label: {
-          string_id: "onboarding-multistage-welcome-secondary-button-label"
-        },
-        action: {
-          data: {
-            entrypoint: "activity-stream-firstrun"
-          },
-          type: "SHOW_FIREFOX_ACCOUNTS",
-          addFlowParams: true
-        }
-      }
-    }
-  }, {
-    id: "AW_IMPORT_SETTINGS",
-    order: 1,
-    content: {
-      zap: true,
-      help_text: {
-        text: {
-          string_id: "onboarding-import-sites-disclaimer"
-        }
-      },
-      title: {
-        string_id: "onboarding-multistage-import-header"
-      },
-      subtitle: {
-        string_id: "onboarding-multistage-import-subtitle"
-      },
-      tiles: {
-        type: "topsites",
-        showTitles: true
-      },
-      primary_button: {
-        label: {
-          string_id: "onboarding-multistage-import-primary-button-label"
-        },
-        action: {
-          type: "SHOW_MIGRATION_WIZARD",
-          navigate: true
-        }
-      },
-      secondary_button: {
-        label: {
-          string_id: "onboarding-multistage-import-secondary-button-label"
-        },
-        action: {
-          navigate: true
-        }
-      }
-    }
-  }, {
-    id: "AW_CHOOSE_THEME",
-    order: 2,
-    content: {
-      zap: true,
-      title: {
-        string_id: "onboarding-multistage-theme-header"
-      },
-      subtitle: {
-        string_id: "onboarding-multistage-theme-subtitle"
-      },
-      tiles: {
-        type: "theme",
-        action: {
-          theme: "<event>"
-        },
-        data: [{
-          theme: "automatic",
-          label: {
-            string_id: "onboarding-multistage-theme-label-automatic"
-          },
-          tooltip: {
-            string_id: "onboarding-multistage-theme-tooltip-automatic-2"
-          },
-          description: {
-            string_id: "onboarding-multistage-theme-description-automatic-2"
-          }
-        }, {
-          theme: "light",
-          label: {
-            string_id: "onboarding-multistage-theme-label-light"
-          },
-          tooltip: {
-            string_id: "onboarding-multistage-theme-tooltip-light-2"
-          },
-          description: {
-            string_id: "onboarding-multistage-theme-description-light"
-          }
-        }, {
-          theme: "dark",
-          label: {
-            string_id: "onboarding-multistage-theme-label-dark"
-          },
-          tooltip: {
-            string_id: "onboarding-multistage-theme-tooltip-dark-2"
-          },
-          description: {
-            string_id: "onboarding-multistage-theme-description-dark"
-          }
-        }, {
-          theme: "alpenglow",
-          label: {
-            string_id: "onboarding-multistage-theme-label-alpenglow"
-          },
-          tooltip: {
-            string_id: "onboarding-multistage-theme-tooltip-alpenglow-2"
-          },
-          description: {
-            string_id: "onboarding-multistage-theme-description-alpenglow"
-          }
-        }]
-      },
-      primary_button: {
-        label: {
-          string_id: "onboarding-multistage-theme-primary-button-label2"
-        },
-        action: {
-          navigate: true
-        }
-      },
-      secondary_button: {
-        label: {
-          string_id: "onboarding-multistage-theme-secondary-button-label"
-        },
-        action: {
-          theme: "automatic",
-          navigate: true
-        }
-      }
-    }
-  }]
-};
 
 /***/ }),
 /* 8 */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "BASE_PARAMS", function() { return BASE_PARAMS; });
new file mode 100644
--- /dev/null
+++ b/browser/components/newtab/aboutwelcome/lib/AboutWelcomeDefaults.jsm
@@ -0,0 +1,314 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const EXPORTED_SYMBOLS = ["AboutWelcomeDefaults"];
+
+const { XPCOMUtils } = ChromeUtils.import(
+  "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+  Services: "resource://gre/modules/Services.jsm",
+  ShellService: "resource:///modules/ShellService.jsm",
+  AttributionCode: "resource:///modules/AttributionCode.jsm",
+  AddonRepository: "resource://gre/modules/addons/AddonRepository.jsm",
+});
+
+const DID_SEE_ABOUT_WELCOME_PREF = "trailhead.firstrun.didSeeAboutWelcome";
+
+const DEFAULT_WELCOME_CONTENT = {
+  template: "multistage",
+  screens: [
+    {
+      id: "AW_SET_DEFAULT",
+      order: 0,
+      content: {
+        zap: true,
+        title: {
+          string_id: "onboarding-multistage-set-default-header",
+        },
+        subtitle: {
+          string_id: "onboarding-multistage-set-default-subtitle",
+        },
+        primary_button: {
+          label: {
+            string_id: "onboarding-multistage-set-default-primary-button-label",
+          },
+          action: {
+            navigate: true,
+            type: "SET_DEFAULT_BROWSER",
+          },
+        },
+        secondary_button: {
+          label: {
+            string_id:
+              "onboarding-multistage-set-default-secondary-button-label",
+          },
+          action: {
+            navigate: true,
+          },
+        },
+        secondary_button_top: {
+          text: {
+            string_id: "onboarding-multistage-welcome-secondary-button-text",
+          },
+          label: {
+            string_id: "onboarding-multistage-welcome-secondary-button-label",
+          },
+          action: {
+            data: {
+              entrypoint: "activity-stream-firstrun",
+            },
+            type: "SHOW_FIREFOX_ACCOUNTS",
+            addFlowParams: true,
+          },
+        },
+      },
+    },
+    {
+      id: "AW_IMPORT_SETTINGS",
+      order: 1,
+      content: {
+        zap: true,
+        help_text: {
+          text: {
+            string_id: "onboarding-import-sites-disclaimer",
+          },
+        },
+        title: {
+          string_id: "onboarding-multistage-import-header",
+        },
+        subtitle: {
+          string_id: "onboarding-multistage-import-subtitle",
+        },
+        tiles: {
+          type: "topsites",
+          showTitles: true,
+        },
+        primary_button: {
+          label: {
+            string_id: "onboarding-multistage-import-primary-button-label",
+          },
+          action: {
+            type: "SHOW_MIGRATION_WIZARD",
+            navigate: true,
+          },
+        },
+        secondary_button: {
+          label: {
+            string_id: "onboarding-multistage-import-secondary-button-label",
+          },
+          action: {
+            navigate: true,
+          },
+        },
+      },
+    },
+    {
+      id: "AW_CHOOSE_THEME",
+      order: 2,
+      content: {
+        zap: true,
+        title: {
+          string_id: "onboarding-multistage-theme-header",
+        },
+        subtitle: {
+          string_id: "onboarding-multistage-theme-subtitle",
+        },
+        tiles: {
+          type: "theme",
+          action: {
+            theme: "<event>",
+          },
+          data: [
+            {
+              theme: "automatic",
+              label: {
+                string_id: "onboarding-multistage-theme-label-automatic",
+              },
+              tooltip: {
+                string_id: "onboarding-multistage-theme-tooltip-automatic-2",
+              },
+              description: {
+                string_id:
+                  "onboarding-multistage-theme-description-automatic-2",
+              },
+            },
+            {
+              theme: "light",
+              label: {
+                string_id: "onboarding-multistage-theme-label-light",
+              },
+              tooltip: {
+                string_id: "onboarding-multistage-theme-tooltip-light-2",
+              },
+              description: {
+                string_id: "onboarding-multistage-theme-description-light",
+              },
+            },
+            {
+              theme: "dark",
+              label: {
+                string_id: "onboarding-multistage-theme-label-dark",
+              },
+              tooltip: {
+                string_id: "onboarding-multistage-theme-tooltip-dark-2",
+              },
+              description: {
+                string_id: "onboarding-multistage-theme-description-dark",
+              },
+            },
+            {
+              theme: "alpenglow",
+              label: {
+                string_id: "onboarding-multistage-theme-label-alpenglow",
+              },
+              tooltip: {
+                string_id: "onboarding-multistage-theme-tooltip-alpenglow-2",
+              },
+              description: {
+                string_id: "onboarding-multistage-theme-description-alpenglow",
+              },
+            },
+          ],
+        },
+        primary_button: {
+          label: {
+            string_id: "onboarding-multistage-theme-primary-button-label2",
+          },
+          action: {
+            navigate: true,
+          },
+        },
+        secondary_button: {
+          label: {
+            string_id: "onboarding-multistage-theme-secondary-button-label",
+          },
+          action: {
+            theme: "automatic",
+            navigate: true,
+          },
+        },
+      },
+    },
+  ],
+};
+
+// Helper function to determine if Windows platform supports
+// automated pinning to taskbar.
+// See https://searchfox.org/mozilla-central/rev/002023eb262be9db3479142355e1675645d52d52/browser/components/shell/nsIWindowsShellService.idl#17
+function canPinCurrentAppToTaskbar() {
+  try {
+    ShellService.QueryInterface(
+      Ci.nsIWindowsShellService
+    ).checkPinCurrentAppToTaskbar();
+    return true;
+  } catch (e) {}
+  return false;
+}
+
+async function getAddonFromRepository(data) {
+  const [addonInfo] = await AddonRepository.getAddonsByIDs([data]);
+  if (addonInfo.sourceURI.scheme !== "https") {
+    return null;
+  }
+  return {
+    name: addonInfo.name,
+    url: addonInfo.sourceURI.spec,
+    iconURL: addonInfo.icons["64"] || addonInfo.icons["32"],
+  };
+}
+
+async function getAddonInfo(attrbObj) {
+  let { content, source } = attrbObj;
+  try {
+    if (!content || source !== "addons.mozilla.org") {
+      return null;
+    }
+    // Attribution data can be double encoded
+    while (content.includes("%")) {
+      try {
+        const result = decodeURIComponent(content);
+        if (result === content) {
+          break;
+        }
+        content = result;
+      } catch (e) {
+        break;
+      }
+    }
+    return await getAddonFromRepository(content);
+  } catch (e) {
+    Cu.reportError("Failed to get the latest add-on version for Return to AMO");
+    return null;
+  }
+}
+
+function hasAMOAttribution(attributionData) {
+  return (
+    attributionData &&
+    attributionData.campaign === "non-fx-button" &&
+    attributionData.source === "addons.mozilla.org"
+  );
+}
+
+async function formatAttributionData(attribution) {
+  if (hasAMOAttribution(attribution)) {
+    let extraProps = await getAddonInfo(attribution);
+    if (extraProps) {
+      return extraProps;
+    }
+  }
+  return null;
+}
+
+async function getAttributionContent() {
+  let attributionContent = await formatAttributionData(
+    await AttributionCode.getAttrDataAsync()
+  );
+
+  if (attributionContent) {
+    return { ...attributionContent, template: "return_to_amo" };
+  }
+
+  return null;
+}
+
+const RULES = [
+  {
+    description: "Windows pin to task bar screen",
+    getDefaults() {
+      if (
+        !Services.prefs.getBoolPref(DID_SEE_ABOUT_WELCOME_PREF, true) &&
+        canPinCurrentAppToTaskbar()
+      ) {
+        return { ...DEFAULT_WELCOME_CONTENT };
+      }
+
+      return null;
+    },
+  },
+  {
+    description: "Default AW content",
+    getDefaults() {
+      return { ...DEFAULT_WELCOME_CONTENT };
+    },
+  },
+];
+
+function getDefaults() {
+  for (const rule of RULES) {
+    const result = rule.getDefaults();
+    if (result) {
+      return result;
+    }
+  }
+  return null;
+}
+
+const AboutWelcomeDefaults = {
+  getDefaults,
+  getAttributionContent,
+};
--- a/browser/components/newtab/content-src/aboutwelcome/aboutwelcome.jsx
+++ b/browser/components/newtab/content-src/aboutwelcome/aboutwelcome.jsx
@@ -2,18 +2,16 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 import React from "react";
 import ReactDOM from "react-dom";
 import { MultiStageAboutWelcome } from "./components/MultiStageAboutWelcome";
 import { ReturnToAMO } from "./components/ReturnToAMO";
 
-import { DEFAULT_WELCOME_CONTENT } from "../lib/aboutwelcome-utils";
-
 class AboutWelcome extends React.PureComponent {
   constructor(props) {
     super(props);
     this.state = { metricsFlowUri: null };
     this.fetchFxAFlowUri = this.fetchFxAFlowUri.bind(this);
   }
 
   async fetchFxAFlowUri() {
@@ -76,18 +74,16 @@ class AboutWelcome extends React.PureCom
         metricsFlowUri={this.state.metricsFlowUri}
         message_id={props.messageId}
         utm_term={props.UTMTerm}
       />
     );
   }
 }
 
-AboutWelcome.defaultProps = DEFAULT_WELCOME_CONTENT;
-
 // Computes messageId and UTMTerm info used in telemetry
 function ComputeTelemetryInfo(welcomeContent, experimentId, branchId) {
   let messageId =
     welcomeContent.template === "return_to_amo"
       ? "RTAMO_DEFAULT_WELCOME"
       : "DEFAULT_ABOUTWELCOME";
   let UTMTerm = "default";
 
@@ -100,46 +96,37 @@ function ComputeTelemetryInfo(welcomeCon
   }
   return {
     messageId,
     UTMTerm,
   };
 }
 
 async function retrieveRenderContent() {
-  // Check for featureConfig and retrieve content
-  const featureConfig = await window.AWGetFeatureConfig();
-  let aboutWelcomeProps;
-
-  if (!featureConfig.screens) {
-    const attribution = await window.AWGetAttributionData();
-    aboutWelcomeProps = {
-      template: attribution.template,
-      ...attribution.extraProps,
-    };
-  } else {
-    // If screens is defined then we have multi stage AW content to show
-    aboutWelcomeProps = featureConfig.screens ? featureConfig : {};
-  }
-
-  // Set design if exists in featureConfig
-  if (featureConfig.design && !aboutWelcomeProps?.design) {
-    aboutWelcomeProps = { ...aboutWelcomeProps, design: featureConfig.design };
-  }
+  // Feature config includes:
+  // user prefs
+  // experiment data
+  // attribution data
+  // defaults
+  let featureConfig = await window.AWGetFeatureConfig();
 
   let { messageId, UTMTerm } = ComputeTelemetryInfo(
-    aboutWelcomeProps,
+    featureConfig,
     featureConfig.slug,
     featureConfig.branch && featureConfig.branch.slug
   );
-  return { aboutWelcomeProps, messageId, UTMTerm };
+  return { featureConfig, messageId, UTMTerm };
 }
 
 async function mount() {
-  let { aboutWelcomeProps, messageId, UTMTerm } = await retrieveRenderContent();
+  let {
+    featureConfig: aboutWelcomeProps,
+    messageId,
+    UTMTerm,
+  } = await retrieveRenderContent();
   ReactDOM.render(
     <AboutWelcome
       messageId={messageId}
       UTMTerm={UTMTerm}
       {...aboutWelcomeProps}
     />,
     document.getElementById("root")
   );
--- a/browser/components/newtab/content-src/lib/aboutwelcome-utils.js
+++ b/browser/components/newtab/content-src/lib/aboutwelcome-utils.js
@@ -75,168 +75,8 @@ export const DEFAULT_RTAMO_CONTENT = {
       },
       message_id: "RTAMO_START_BROWSING_BUTTON",
       action: {
         type: "OPEN_AWESOME_BAR",
       },
     },
   },
 };
-
-export const DEFAULT_WELCOME_CONTENT = {
-  template: "multistage",
-  screens: [
-    {
-      id: "AW_SET_DEFAULT",
-      order: 0,
-      content: {
-        zap: true,
-        title: {
-          string_id: "onboarding-multistage-set-default-header",
-        },
-        subtitle: { string_id: "onboarding-multistage-set-default-subtitle" },
-        primary_button: {
-          label: {
-            string_id: "onboarding-multistage-set-default-primary-button-label",
-          },
-          action: {
-            navigate: true,
-            type: "SET_DEFAULT_BROWSER",
-          },
-        },
-        secondary_button: {
-          label: {
-            string_id:
-              "onboarding-multistage-set-default-secondary-button-label",
-          },
-          action: {
-            navigate: true,
-          },
-        },
-        secondary_button_top: {
-          text: {
-            string_id: "onboarding-multistage-welcome-secondary-button-text",
-          },
-          label: {
-            string_id: "onboarding-multistage-welcome-secondary-button-label",
-          },
-          action: {
-            data: { entrypoint: "activity-stream-firstrun" },
-            type: "SHOW_FIREFOX_ACCOUNTS",
-            addFlowParams: true,
-          },
-        },
-      },
-    },
-    {
-      id: "AW_IMPORT_SETTINGS",
-      order: 1,
-      content: {
-        zap: true,
-        help_text: {
-          text: { string_id: "onboarding-import-sites-disclaimer" },
-        },
-        title: { string_id: "onboarding-multistage-import-header" },
-        subtitle: { string_id: "onboarding-multistage-import-subtitle" },
-        tiles: {
-          type: "topsites",
-          showTitles: true,
-        },
-        primary_button: {
-          label: {
-            string_id: "onboarding-multistage-import-primary-button-label",
-          },
-          action: {
-            type: "SHOW_MIGRATION_WIZARD",
-            navigate: true,
-          },
-        },
-        secondary_button: {
-          label: {
-            string_id: "onboarding-multistage-import-secondary-button-label",
-          },
-          action: {
-            navigate: true,
-          },
-        },
-      },
-    },
-    {
-      id: "AW_CHOOSE_THEME",
-      order: 2,
-      content: {
-        zap: true,
-        title: { string_id: "onboarding-multistage-theme-header" },
-        subtitle: { string_id: "onboarding-multistage-theme-subtitle" },
-        tiles: {
-          type: "theme",
-          action: {
-            theme: "<event>",
-          },
-          data: [
-            {
-              theme: "automatic",
-              label: {
-                string_id: "onboarding-multistage-theme-label-automatic",
-              },
-              tooltip: {
-                string_id: "onboarding-multistage-theme-tooltip-automatic-2",
-              },
-              description: {
-                string_id:
-                  "onboarding-multistage-theme-description-automatic-2",
-              },
-            },
-            {
-              theme: "light",
-              label: { string_id: "onboarding-multistage-theme-label-light" },
-              tooltip: {
-                string_id: "onboarding-multistage-theme-tooltip-light-2",
-              },
-              description: {
-                string_id: "onboarding-multistage-theme-description-light",
-              },
-            },
-            {
-              theme: "dark",
-              label: { string_id: "onboarding-multistage-theme-label-dark" },
-              tooltip: {
-                string_id: "onboarding-multistage-theme-tooltip-dark-2",
-              },
-              description: {
-                string_id: "onboarding-multistage-theme-description-dark",
-              },
-            },
-            {
-              theme: "alpenglow",
-              label: {
-                string_id: "onboarding-multistage-theme-label-alpenglow",
-              },
-              tooltip: {
-                string_id: "onboarding-multistage-theme-tooltip-alpenglow-2",
-              },
-              description: {
-                string_id: "onboarding-multistage-theme-description-alpenglow",
-              },
-            },
-          ],
-        },
-        primary_button: {
-          label: {
-            string_id: "onboarding-multistage-theme-primary-button-label2",
-          },
-          action: {
-            navigate: true,
-          },
-        },
-        secondary_button: {
-          label: {
-            string_id: "onboarding-multistage-theme-secondary-button-label",
-          },
-          action: {
-            theme: "automatic",
-            navigate: true,
-          },
-        },
-      },
-    },
-  ],
-};
--- a/browser/components/newtab/test/browser/browser_aboutwelcome_multistage.js
+++ b/browser/components/newtab/test/browser/browser_aboutwelcome_multistage.js
@@ -842,25 +842,25 @@ add_task(async function test_AWMultistag
   );
   Assert.equal(
     eventCall.args[0],
     "AWPage:TELEMETRY_EVENT",
     "Got call to handle Telemetry event"
   );
 });
 
-add_task(async function test_onContentMessage() {
+add_task(async function test_updatesPrefOnAWOpen() {
   Services.prefs.setBoolPref(DID_SEE_ABOUT_WELCOME_PREF, false);
   await setAboutWelcomePref(true);
 
   await openAboutWelcome();
-  Assert.equal(
-    Services.prefs.getBoolPref(DID_SEE_ABOUT_WELCOME_PREF, false),
-    true,
-    "Pref was set"
+  await BrowserTestUtils.waitForCondition(
+    () =>
+      Services.prefs.getBoolPref(DID_SEE_ABOUT_WELCOME_PREF, false) === true,
+    "Updated pref to seen AW"
   );
   Services.prefs.clearUserPref(DID_SEE_ABOUT_WELCOME_PREF);
 });
 
 // Test Fxaccounts MetricsFlowURI
 test_newtab(
   {
     async before({ pushPrefs }) {
--- a/browser/components/newtab/test/unit/aboutwelcome/MultiStageAboutWelcome.test.jsx
+++ b/browser/components/newtab/test/unit/aboutwelcome/MultiStageAboutWelcome.test.jsx
@@ -1,19 +1,19 @@
 import { GlobalOverrider } from "test/unit/utils";
 import {
   MultiStageAboutWelcome,
   WelcomeScreen,
 } from "content-src/aboutwelcome/components/MultiStageAboutWelcome";
 import React from "react";
 import { shallow, mount } from "enzyme";
-import {
-  DEFAULT_WELCOME_CONTENT,
-  AboutWelcomeUtils,
-} from "content-src/lib/aboutwelcome-utils";
+import { AboutWelcomeDefaults } from "aboutwelcome/lib/AboutWelcomeDefaults.jsm";
+import { AboutWelcomeUtils } from "content-src/lib/aboutwelcome-utils";
+
+const DEFAULT_WELCOME_CONTENT = AboutWelcomeDefaults.getDefaults();
 
 describe("MultiStageAboutWelcome module", () => {
   let globals;
   let sandbox;
 
   const DEFAULT_PROPS = {
     screens: DEFAULT_WELCOME_CONTENT.screens,
     metricsFlowUri: "http://localhost/",
--- a/browser/components/newtab/test/xpcshell/test_AboutWelcomeAttribution.js
+++ b/browser/components/newtab/test/xpcshell/test_AboutWelcomeAttribution.js
@@ -1,38 +1,54 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 "use strict";
 
-const { AboutWelcomeChild } = ChromeUtils.import(
-  "resource:///actors/AboutWelcomeChild.jsm"
+const { AboutWelcomeDefaults } = ChromeUtils.import(
+  "resource://activity-stream/aboutwelcome/lib/AboutWelcomeDefaults.jsm"
 );
 const { sinon } = ChromeUtils.import("resource://testing-common/Sinon.jsm");
+const { AttributionCode } = ChromeUtils.import(
+  "resource:///modules/AttributionCode.jsm"
+);
+const { AddonRepository } = ChromeUtils.import(
+  "resource://gre/modules/addons/AddonRepository.jsm"
+);
 
 const TEST_ATTRIBUTION_DATA = {
   source: "addons.mozilla.org",
   medium: "referral",
   campaign: "non-fx-button",
   content: "iridium%40particlecore.github.io",
 };
 
 add_task(async function test_handleAddonInfoNotFound() {
-  let AWChild = new AboutWelcomeChild();
-  const stub = sinon.stub(AWChild, "getAddonInfo").resolves(null);
-  let result = await AWChild.formatAttributionData(TEST_ATTRIBUTION_DATA);
+  let sandbox = sinon.createSandbox();
+  const stub = sandbox.stub(AttributionCode, "getAttrDataAsync").resolves(null);
+  let result = await AboutWelcomeDefaults.getAttributionContent();
   equal(stub.callCount, 1, "Call was made");
-  equal(result.template, undefined, "No template returned");
+  equal(result, null, "No data is returned");
+
+  sandbox.restore();
 });
 
 add_task(async function test_formatAttributionData() {
-  let AWChild = new AboutWelcomeChild();
+  let sandbox = sinon.createSandbox();
   const TEST_ADDON_INFO = {
+    sourceURI: { scheme: "https", spec: "https://test.xpi" },
     name: "Test Add-on",
-    url: "https://test.xpi",
-    iconURL: "http://test.svg",
+    icons: { "64": "http://test.svg" },
   };
-  sinon.stub(AWChild, "getAddonInfo").resolves(TEST_ADDON_INFO);
-  let result = await AWChild.formatAttributionData(TEST_ATTRIBUTION_DATA);
+  sandbox
+    .stub(AttributionCode, "getAttrDataAsync")
+    .resolves(TEST_ATTRIBUTION_DATA);
+  sandbox.stub(AddonRepository, "getAddonsByIDs").resolves([TEST_ADDON_INFO]);
+  let result = await AboutWelcomeDefaults.getAttributionContent(
+    TEST_ATTRIBUTION_DATA
+  );
+  equal(AddonRepository.getAddonsByIDs.callCount, 1, "Retrieve addon content");
   equal(result.template, "return_to_amo", "RTAMO template returned");
-  equal(result.extraProps, TEST_ADDON_INFO, "AddonInfo returned");
+  equal(result.name, TEST_ADDON_INFO.name, "AddonInfo returned");
+
+  sandbox.restore();
 });
--- a/toolkit/components/nimbus/ExperimentAPI.jsm
+++ b/toolkit/components/nimbus/ExperimentAPI.jsm
@@ -319,17 +319,17 @@ const ExperimentAPI = {
     }
   },
 };
 
 class ExperimentFeature {
   static MANIFEST = MANIFEST;
   constructor(featureId, manifest) {
     this.featureId = featureId;
-    this.defaultPrefValues = {};
+    this.prefGetters = {};
     this.manifest = manifest || ExperimentFeature.MANIFEST[featureId];
     if (!this.manifest) {
       Cu.reportError(
         `No manifest entry for ${featureId}. Please add one to toolkit/components/messaging-system/experiments/ExperimentAPI.jsm`
       );
     }
     const variables = this.manifest?.variables || {};
 
@@ -348,32 +348,48 @@ class ExperimentFeature {
         }
       );
     }
 
     Object.keys(variables).forEach(key => {
       const { type, fallbackPref } = variables[key];
       if (fallbackPref) {
         XPCOMUtils.defineLazyPreferenceGetter(
-          this.defaultPrefValues,
+          this.prefGetters,
           key,
           fallbackPref,
           null,
           () => {
             ExperimentAPI._store._emitFeatureUpdate(
               this.featureId,
               "pref-updated"
             );
           },
           type === "json" ? parseJSON : val => val
         );
       }
     });
   }
 
+  _getUserPrefsValues() {
+    let userPrefs = {};
+    Object.keys(this.manifest?.variables || {}).forEach(variable => {
+      if (
+        this.manifest.variables[variable].fallbackPref &&
+        Services.prefs.prefHasUserValue(
+          this.manifest.variables[variable].fallbackPref
+        )
+      ) {
+        userPrefs[variable] = this.prefGetters[variable];
+      }
+    });
+
+    return userPrefs;
+  }
+
   ready() {
     return ExperimentAPI.ready();
   }
 
   /**
    * Lookup feature in active experiments and return enabled.
    * By default, this will send an exposure event.
    * @param {{sendExposureEvent: boolean, defaultValue?: any}} options
@@ -400,28 +416,28 @@ class ExperimentFeature {
   }
 
   /**
    * Lookup feature in active experiments and return value.
    * By default, this will send an exposure event.
    * @param {{sendExposureEvent: boolean, defaultValue?: any}} options
    * @returns {obj} The feature value
    */
-  getValue({ sendExposureEvent, defaultValue = null } = {}) {
+  getValue({ sendExposureEvent } = {}) {
+    // Any user pref will override any other configuration
+    let userPrefs = this._getUserPrefsValues();
     const branch = ExperimentAPI.activateBranch({
       featureId: this.featureId,
       sendExposureEvent,
     });
     if (branch?.feature?.value) {
-      return branch.feature.value;
+      return { ...branch.feature.value, ...userPrefs };
     }
 
-    return Object.keys(this.defaultPrefValues).length
-      ? this.defaultPrefValues
-      : defaultValue;
+    return this.prefGetters;
   }
 
   recordExposureEvent() {
     ExperimentAPI.activateBranch({
       featureId: this.featureId,
       sendExposureEvent: true,
     });
   }
@@ -437,21 +453,22 @@ class ExperimentFeature {
   debug() {
     return {
       enabled: this.isEnabled(),
       value: this.getValue(),
       experiment: ExperimentAPI.getExperimentMetaData({
         featureId: this.featureId,
       }),
       fallbackPrefs:
-        this.defaultPrefValues &&
-        Object.keys(this.defaultPrefValues).map(prefName => [
+        this.prefGetters &&
+        Object.keys(this.prefGetters).map(prefName => [
           prefName,
-          this.defaultPrefValues[prefName],
+          this.prefGetters[prefName],
         ]),
+      userPrefs: this._getUserPrefsValues(),
     };
   }
 }
 
 XPCOMUtils.defineLazyGetter(ExperimentAPI, "_store", function() {
   return IS_MAIN_PROCESS ? ExperimentManager.store : new ExperimentStore();
 });
 
--- a/toolkit/components/nimbus/docs/integration.md
+++ b/toolkit/components/nimbus/docs/integration.md
@@ -62,18 +62,17 @@ const props = feature.getValue();
 renderSomeUI(props);
 ```
 
 Defaults values inline:
 
 ```jsx
 feature.isEnabled({ defaultValue: true });
 
-// Default values work here too
-const { skipFocus } = feature.getValue({ defaultValue: { skipFocus: false } });
+const { skipFocus } = feature.getValue() || {};
 ```
 
 Listen to changes:
 
 ```jsx
 // Listen to changes, including to fallback prefs.
 feature.on(() => {
   updateUI(feature.getValue());
--- a/toolkit/components/nimbus/test/unit/test_ExperimentAPI_ExperimentFeature.js
+++ b/toolkit/components/nimbus/test/unit/test_ExperimentAPI_ExperimentFeature.js
@@ -32,16 +32,21 @@ async function setupForExperimentFeature
   const manager = ExperimentFakes.manager();
   await manager.onStartup();
 
   sandbox.stub(ExperimentAPI, "_store").get(() => manager.store);
 
   return { sandbox, manager };
 }
 
+function setDefaultBranch(pref, value) {
+  let branch = Services.prefs.getDefaultBranch("");
+  branch.setStringPref(pref, value);
+}
+
 const TEST_FALLBACK_PREF = "testprefbranch.config";
 const FAKE_FEATURE_MANIFEST = {
   enabledFallbackPref: "testprefbranch.enabled",
   variables: {
     config: {
       type: "json",
       fallbackPref: TEST_FALLBACK_PREF,
     },
@@ -101,24 +106,16 @@ add_task(async function test_ExperimentF
 /**
  * # ExperimentFeature.getValue
  */
 add_task(async function test_ExperimentFeature_getValue() {
   const { sandbox } = await setupForExperimentFeature();
 
   const featureInstance = new ExperimentFeature("foo", FAKE_FEATURE_MANIFEST);
 
-  Services.prefs.clearUserPref("testprefbranch.value");
-
-  Assert.deepEqual(
-    featureInstance.getValue({ defaultValue: { hello: 1 } }),
-    { hello: 1 },
-    "should return the defaultValue if no fallback pref is set"
-  );
-
   Services.prefs.setStringPref(TEST_FALLBACK_PREF, `{"bar": 123}`);
 
   Assert.deepEqual(
     featureInstance.getValue().config,
     { bar: 123 },
     "should return the fallback pref value"
   );
 
@@ -140,17 +137,17 @@ add_task(
           enabled: true,
           value: { whoa: true },
         },
       },
     });
 
     manager.store.addExperiment(expected);
 
-    Services.prefs.setStringPref(TEST_FALLBACK_PREF, `{"bar": 123}`);
+    setDefaultBranch(TEST_FALLBACK_PREF, `{"bar": 123}`);
 
     Assert.deepEqual(
       featureInstance.getValue(),
       { whoa: true },
       "should return the experiment feature value, not the fallback one."
     );
 
     Services.prefs.clearUserPref("testprefbranch.value");
new file mode 100644
--- /dev/null
+++ b/toolkit/components/nimbus/test/unit/test_ExperimentAPI_ExperimentFeature_getValue.js
@@ -0,0 +1,110 @@
+"use strict";
+
+const { ExperimentAPI, ExperimentFeature } = ChromeUtils.import(
+  "resource://nimbus/ExperimentAPI.jsm"
+);
+const { ExperimentFakes } = ChromeUtils.import(
+  "resource://testing-common/NimbusTestUtils.jsm"
+);
+const { TestUtils } = ChromeUtils.import(
+  "resource://testing-common/TestUtils.jsm"
+);
+
+const { XPCOMUtils } = ChromeUtils.import(
+  "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+async function setupForExperimentFeature() {
+  const sandbox = sinon.createSandbox();
+  const manager = ExperimentFakes.manager();
+  await manager.onStartup();
+
+  sandbox.stub(ExperimentAPI, "_store").get(() => manager.store);
+
+  return { sandbox, manager };
+}
+
+const FEATURE_ID = "aboutwelcome";
+const TEST_FALLBACK_PREF = "browser.aboutwelcome.screens";
+const FAKE_FEATURE_MANIFEST = {
+  variables: {
+    screens: {
+      type: "json",
+      fallbackPref: TEST_FALLBACK_PREF,
+    },
+  },
+};
+
+add_task(async function test_ExperimentFeature_getValue_prefsOverDefaults() {
+  const { sandbox } = await setupForExperimentFeature();
+
+  const featureInstance = new ExperimentFeature(
+    FEATURE_ID,
+    FAKE_FEATURE_MANIFEST
+  );
+
+  Services.prefs.clearUserPref(TEST_FALLBACK_PREF);
+
+  Assert.equal(
+    featureInstance.getValue().screens?.length,
+    undefined,
+    "pref is not set"
+  );
+
+  Services.prefs.setStringPref(TEST_FALLBACK_PREF, "[]");
+
+  Assert.deepEqual(
+    featureInstance.getValue().screens.length,
+    0,
+    "should return the user pref value over the defaults"
+  );
+
+  Services.prefs.clearUserPref(TEST_FALLBACK_PREF);
+  sandbox.restore();
+});
+
+add_task(async function test_ExperimentFeature_getValue_prefsOverExperiment() {
+  const { sandbox, manager } = await setupForExperimentFeature();
+  const recipe = ExperimentFakes.experiment("awexperiment", {
+    branch: {
+      slug: "treatment",
+      feature: {
+        featureId: "aboutwelcome",
+        enabled: true,
+        value: { screens: ["test-value"] },
+      },
+    },
+  });
+
+  manager.store.addExperiment(recipe);
+
+  const featureInstance = new ExperimentFeature(
+    FEATURE_ID,
+    FAKE_FEATURE_MANIFEST
+  );
+
+  Services.prefs.clearUserPref(TEST_FALLBACK_PREF);
+
+  Assert.ok(
+    !!featureInstance.getValue().screens,
+    "should return the AW experiment value"
+  );
+
+  Assert.equal(
+    featureInstance.getValue().screens[0],
+    "test-value",
+    "should return the AW experiment value"
+  );
+
+  Services.prefs.setStringPref(TEST_FALLBACK_PREF, "[]");
+
+  Assert.deepEqual(
+    featureInstance.getValue().screens.length,
+    0,
+    "should return the user pref value"
+  );
+
+  Services.prefs.clearUserPref(TEST_FALLBACK_PREF);
+  manager.store._deleteForTests(recipe.slug);
+  sandbox.restore();
+});
--- a/toolkit/components/nimbus/test/unit/xpcshell.ini
+++ b/toolkit/components/nimbus/test/unit/xpcshell.ini
@@ -10,10 +10,11 @@ support-files =
 [test_ExperimentManager_lifecycle.js]
 [test_ExperimentManager_unenroll.js]
 [test_ExperimentManager_generateTestIds.js]
 [test_ExperimentStore.js]
 [test_NimbusTestUtils.js]
 [test_SharedDataMap.js]
 [test_ExperimentAPI.js]
 [test_ExperimentAPI_ExperimentFeature.js]
+[test_ExperimentAPI_ExperimentFeature_getValue.js]
 [test_RemoteSettingsExperimentLoader.js]
 [test_RemoteSettingsExperimentLoader_updateRecipes.js]