Bug 1694257 - Add ability for targeted defaults for AboutWelcome r=k88hudson

Differential Revision: https://phabricator.services.mozilla.com/D106418
This commit is contained in:
Andrei Oprea 2021-03-16 17:49:41 +00:00
Родитель d8f842d8c1
Коммит 2bb63845fa
14 изменённых файлов: 551 добавлений и 516 удалений

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

@ -15,6 +15,8 @@ XPCOMUtils.defineLazyModuleGetters(this, {
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", () => {
@ -156,10 +158,6 @@ class AboutWelcomeChild extends JSWindowActorChild {
defineAs: "AWGetFeatureConfig",
});
Cu.exportFunction(this.AWGetAttributionData.bind(this), window, {
defineAs: "AWGetAttributionData",
});
Cu.exportFunction(this.AWGetFxAMetricsFlowURI.bind(this), window, {
defineAs: "AWGetFxAMetricsFlowURI",
});
@ -216,79 +214,15 @@ class AboutWelcomeChild extends JSWindowActorChild {
);
}
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(
@ -296,18 +230,35 @@ class AboutWelcomeChild extends JSWindowActorChild {
);
} else {
log.debug("Loading about:welcome without experiment");
}
return Cu.cloneInto(
{
// All experimentation right now is using the multistage template
template: "multistage",
...experimentMetadata,
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(
{ ...experimentMetadata, ...featureConfig },
this.contentWindow
);
}
AWGetFeatureConfig() {
return this.wrapPromise(this.getAWContent());
}
AWGetFxAMetricsFlowURI() {
return this.wrapPromise(this.sendQuery("AWPage:FXA_METRICS_FLOW_URI"));
}

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

@ -13,7 +13,6 @@ const { XPCOMUtils } = ChromeUtils.import(
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",
@ -21,7 +20,8 @@ XPCOMUtils.defineLazyModuleGetters(this, {
"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",
});
@ -224,8 +224,6 @@ class AboutWelcomeParent extends JSWindowActorParent {
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":
@ -235,16 +233,9 @@ class AboutWelcomeParent extends JSWindowActorParent {
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]

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

@ -102,7 +102,6 @@ __webpack_require__.r(__webpack_exports__);
/* 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
@ -113,7 +112,6 @@ function _extends() { _extends = Object.assign || function (target) { for (var i
class AboutWelcome extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComponent {
constructor(props) {
super(props);
@ -189,9 +187,8 @@ class AboutWelcome extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComp
});
}
}
} // 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";
@ -212,36 +209,18 @@ function ComputeTelemetryInfo(welcomeContent, experimentId, branchId) {
}
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
};
@ -249,7 +228,7 @@ async function retrieveRenderContent() {
async function mount() {
let {
aboutWelcomeProps,
featureConfig: aboutWelcomeProps,
messageId,
UTMTerm
} = await retrieveRenderContent();
@ -880,7 +859,6 @@ const HelpText = props => {
__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/. */
@ -985,172 +963,6 @@ const DEFAULT_RTAMO_CONTENT = {
}
}
};
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 */

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

@ -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,
};

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

@ -7,8 +7,6 @@ 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);
@ -81,8 +79,6 @@ class AboutWelcome extends React.PureComponent {
}
}
AboutWelcome.defaultProps = DEFAULT_WELCOME_CONTENT;
// Computes messageId and UTMTerm info used in telemetry
function ComputeTelemetryInfo(welcomeContent, experimentId, branchId) {
let messageId =
@ -105,36 +101,27 @@ function ComputeTelemetryInfo(welcomeContent, experimentId, branchId) {
}
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}

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

@ -80,163 +80,3 @@ export const DEFAULT_RTAMO_CONTENT = {
},
},
};
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,
},
},
},
},
],
};

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

@ -847,15 +847,15 @@ add_task(async function test_AWMultistage_Import() {
);
});
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);
});

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

@ -5,10 +5,10 @@ import {
} 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;

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

@ -4,10 +4,16 @@
"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",
@ -17,22 +23,32 @@ const TEST_ATTRIBUTION_DATA = {
};
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();
});

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

@ -324,7 +324,7 @@ 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(
@ -353,7 +353,7 @@ class ExperimentFeature {
const { type, fallbackPref } = variables[key];
if (fallbackPref) {
XPCOMUtils.defineLazyPreferenceGetter(
this.defaultPrefValues,
this.prefGetters,
key,
fallbackPref,
null,
@ -369,6 +369,22 @@ class ExperimentFeature {
});
}
_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();
}
@ -405,18 +421,18 @@ class ExperimentFeature {
* @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() {
@ -442,11 +458,12 @@ class ExperimentFeature {
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(),
};
}
}

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

@ -67,8 +67,7 @@ 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:

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

@ -37,6 +37,11 @@ async function setupForExperimentFeature() {
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",
@ -106,14 +111,6 @@ add_task(async function test_ExperimentFeature_getValue() {
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(
@ -145,7 +142,7 @@ add_task(
manager.store.addExperiment(expected);
Services.prefs.setStringPref(TEST_FALLBACK_PREF, `{"bar": 123}`);
setDefaultBranch(TEST_FALLBACK_PREF, `{"bar": 123}`);
Assert.deepEqual(
featureInstance.getValue(),

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

@ -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();
});

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

@ -15,5 +15,6 @@ support-files =
[test_SharedDataMap.js]
[test_ExperimentAPI.js]
[test_ExperimentAPI_ExperimentFeature.js]
[test_ExperimentAPI_ExperimentFeature_getValue.js]
[test_RemoteSettingsExperimentLoader.js]
[test_RemoteSettingsExperimentLoader_updateRecipes.js]