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", ExperimentAPI: "resource://nimbus/ExperimentAPI.jsm",
shortURL: "resource://activity-stream/lib/ShortURL.jsm", shortURL: "resource://activity-stream/lib/ShortURL.jsm",
TippyTopProvider: "resource://activity-stream/lib/TippyTopProvider.jsm", TippyTopProvider: "resource://activity-stream/lib/TippyTopProvider.jsm",
AboutWelcomeDefaults:
"resource://activity-stream/aboutwelcome/lib/AboutWelcomeDefaults.jsm",
}); });
XPCOMUtils.defineLazyGetter(this, "log", () => { XPCOMUtils.defineLazyGetter(this, "log", () => {
@ -156,10 +158,6 @@ class AboutWelcomeChild extends JSWindowActorChild {
defineAs: "AWGetFeatureConfig", defineAs: "AWGetFeatureConfig",
}); });
Cu.exportFunction(this.AWGetAttributionData.bind(this), window, {
defineAs: "AWGetAttributionData",
});
Cu.exportFunction(this.AWGetFxAMetricsFlowURI.bind(this), window, { Cu.exportFunction(this.AWGetFxAMetricsFlowURI.bind(this), window, {
defineAs: "AWGetFxAMetricsFlowURI", 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 * Send initial data to page including experiment information
*/ */
AWGetFeatureConfig() { async getAWContent() {
// 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
let experimentMetadata = let experimentMetadata =
ExperimentAPI.getExperimentMetaData({ ExperimentAPI.getExperimentMetaData({
featureId: "aboutwelcome", featureId: "aboutwelcome",
}) || {}; }) || {};
let featureConfig = aboutWelcomeFeature.getValue({ defaultValue: {} }); let featureConfig = aboutWelcomeFeature.getValue() || {};
if (experimentMetadata?.slug) { if (experimentMetadata?.slug) {
log.debug( log.debug(
@ -296,18 +230,35 @@ class AboutWelcomeChild extends JSWindowActorChild {
); );
} else { } else {
log.debug("Loading about:welcome without experiment"); log.debug("Loading about:welcome without experiment");
} let attributionData = await this.sendQuery("AWPage:GET_ATTRIBUTION_DATA");
return Cu.cloneInto( if (attributionData) {
{ log.debug("Loading about:welcome with attribution data");
// All experimentation right now is using the multistage template featureConfig = { ...attributionData, ...featureConfig };
template: "multistage", } else {
...experimentMetadata, 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, ...featureConfig,
}, screens,
};
}
}
return Cu.cloneInto(
{ ...experimentMetadata, ...featureConfig },
this.contentWindow this.contentWindow
); );
} }
AWGetFeatureConfig() {
return this.wrapPromise(this.getAWContent());
}
AWGetFxAMetricsFlowURI() { AWGetFxAMetricsFlowURI() {
return this.wrapPromise(this.sendQuery("AWPage:FXA_METRICS_FLOW_URI")); return this.wrapPromise(this.sendQuery("AWPage:FXA_METRICS_FLOW_URI"));
} }

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

@ -13,7 +13,6 @@ const { XPCOMUtils } = ChromeUtils.import(
XPCOMUtils.defineLazyModuleGetters(this, { XPCOMUtils.defineLazyModuleGetters(this, {
AddonManager: "resource://gre/modules/AddonManager.jsm", AddonManager: "resource://gre/modules/AddonManager.jsm",
AddonRepository: "resource://gre/modules/addons/AddonRepository.jsm",
FxAccounts: "resource://gre/modules/FxAccounts.jsm", FxAccounts: "resource://gre/modules/FxAccounts.jsm",
MigrationUtils: "resource:///modules/MigrationUtils.jsm", MigrationUtils: "resource:///modules/MigrationUtils.jsm",
OS: "resource://gre/modules/osfile.jsm", OS: "resource://gre/modules/osfile.jsm",
@ -21,7 +20,8 @@ XPCOMUtils.defineLazyModuleGetters(this, {
"resource://messaging-system/lib/SpecialMessageActions.jsm", "resource://messaging-system/lib/SpecialMessageActions.jsm",
AboutWelcomeTelemetry: AboutWelcomeTelemetry:
"resource://activity-stream/aboutwelcome/lib/AboutWelcomeTelemetry.jsm", "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", PromiseUtils: "resource://gre/modules/PromiseUtils.jsm",
Region: "resource://gre/modules/Region.jsm", Region: "resource://gre/modules/Region.jsm",
}); });
@ -224,8 +224,6 @@ class AboutWelcomeParent extends JSWindowActorParent {
break; break;
case "AWPage:FXA_METRICS_FLOW_URI": case "AWPage:FXA_METRICS_FLOW_URI":
return FxAccounts.config.promiseMetricsFlowURI("aboutwelcome"); return FxAccounts.config.promiseMetricsFlowURI("aboutwelcome");
case "AWPage:GET_ATTRIBUTION_DATA":
return AttributionCode.getAttrDataAsync();
case "AWPage:IMPORTABLE_SITES": case "AWPage:IMPORTABLE_SITES":
return getImportableSites(); return getImportableSites();
case "AWPage:TELEMETRY_EVENT": case "AWPage:TELEMETRY_EVENT":
@ -235,16 +233,9 @@ class AboutWelcomeParent extends JSWindowActorParent {
this.AboutWelcomeObserver.terminateReason = this.AboutWelcomeObserver.terminateReason =
AWTerminate.ADDRESS_BAR_NAVIGATED; AWTerminate.ADDRESS_BAR_NAVIGATED;
break; break;
case "AWPage:GET_ADDON_FROM_REPOSITORY": case "AWPage:GET_ATTRIBUTION_DATA":
const [addonInfo] = await AddonRepository.getAddonsByIDs([data]); let attributionData = await AboutWelcomeDefaults.getAttributionContent();
if (addonInfo.sourceURI.scheme !== "https") { return attributionData;
return null;
}
return {
name: addonInfo.name,
url: addonInfo.sourceURI.spec,
iconURL: addonInfo.icons["64"] || addonInfo.icons["32"],
};
case "AWPage:SELECT_THEME": case "AWPage:SELECT_THEME":
return AddonManager.getAddonByID( return AddonManager.getAddonByID(
LIGHT_WEIGHT_THEMES[data] 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 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_MultiStageAboutWelcome__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(3);
/* harmony import */ var _components_ReturnToAMO__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(9); /* 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); } 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 /* 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 { class AboutWelcome extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComponent {
constructor(props) { constructor(props) {
super(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) { function ComputeTelemetryInfo(welcomeContent, experimentId, branchId) {
let messageId = welcomeContent.template === "return_to_amo" ? "RTAMO_DEFAULT_WELCOME" : "DEFAULT_ABOUTWELCOME"; let messageId = welcomeContent.template === "return_to_amo" ? "RTAMO_DEFAULT_WELCOME" : "DEFAULT_ABOUTWELCOME";
@ -212,36 +209,18 @@ function ComputeTelemetryInfo(welcomeContent, experimentId, branchId) {
} }
async function retrieveRenderContent() { async function retrieveRenderContent() {
var _aboutWelcomeProps; // Feature config includes:
// user prefs
// Check for featureConfig and retrieve content // experiment data
const featureConfig = await window.AWGetFeatureConfig(); // attribution data
let aboutWelcomeProps; // defaults
let featureConfig = await window.AWGetFeatureConfig();
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
};
}
let { let {
messageId, messageId,
UTMTerm UTMTerm
} = ComputeTelemetryInfo(aboutWelcomeProps, featureConfig.slug, featureConfig.branch && featureConfig.branch.slug); } = ComputeTelemetryInfo(featureConfig, featureConfig.slug, featureConfig.branch && featureConfig.branch.slug);
return { return {
aboutWelcomeProps, featureConfig,
messageId, messageId,
UTMTerm UTMTerm
}; };
@ -249,7 +228,7 @@ async function retrieveRenderContent() {
async function mount() { async function mount() {
let { let {
aboutWelcomeProps, featureConfig: aboutWelcomeProps,
messageId, messageId,
UTMTerm UTMTerm
} = await retrieveRenderContent(); } = await retrieveRenderContent();
@ -880,7 +859,6 @@ const HelpText = props => {
__webpack_require__.r(__webpack_exports__); __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__, "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_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 /* 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, * 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/. */ * 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 */ /* 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 { MultiStageAboutWelcome } from "./components/MultiStageAboutWelcome";
import { ReturnToAMO } from "./components/ReturnToAMO"; import { ReturnToAMO } from "./components/ReturnToAMO";
import { DEFAULT_WELCOME_CONTENT } from "../lib/aboutwelcome-utils";
class AboutWelcome extends React.PureComponent { class AboutWelcome extends React.PureComponent {
constructor(props) { constructor(props) {
super(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 // Computes messageId and UTMTerm info used in telemetry
function ComputeTelemetryInfo(welcomeContent, experimentId, branchId) { function ComputeTelemetryInfo(welcomeContent, experimentId, branchId) {
let messageId = let messageId =
@ -105,36 +101,27 @@ function ComputeTelemetryInfo(welcomeContent, experimentId, branchId) {
} }
async function retrieveRenderContent() { async function retrieveRenderContent() {
// Check for featureConfig and retrieve content // Feature config includes:
const featureConfig = await window.AWGetFeatureConfig(); // user prefs
let aboutWelcomeProps; // experiment data
// attribution data
if (!featureConfig.screens) { // defaults
const attribution = await window.AWGetAttributionData(); let featureConfig = await window.AWGetFeatureConfig();
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 };
}
let { messageId, UTMTerm } = ComputeTelemetryInfo( let { messageId, UTMTerm } = ComputeTelemetryInfo(
aboutWelcomeProps, featureConfig,
featureConfig.slug, featureConfig.slug,
featureConfig.branch && featureConfig.branch.slug featureConfig.branch && featureConfig.branch.slug
); );
return { aboutWelcomeProps, messageId, UTMTerm }; return { featureConfig, messageId, UTMTerm };
} }
async function mount() { async function mount() {
let { aboutWelcomeProps, messageId, UTMTerm } = await retrieveRenderContent(); let {
featureConfig: aboutWelcomeProps,
messageId,
UTMTerm,
} = await retrieveRenderContent();
ReactDOM.render( ReactDOM.render(
<AboutWelcome <AboutWelcome
messageId={messageId} 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); Services.prefs.setBoolPref(DID_SEE_ABOUT_WELCOME_PREF, false);
await setAboutWelcomePref(true); await setAboutWelcomePref(true);
await openAboutWelcome(); await openAboutWelcome();
Assert.equal( await BrowserTestUtils.waitForCondition(
Services.prefs.getBoolPref(DID_SEE_ABOUT_WELCOME_PREF, false), () =>
true, Services.prefs.getBoolPref(DID_SEE_ABOUT_WELCOME_PREF, false) === true,
"Pref was set" "Updated pref to seen AW"
); );
Services.prefs.clearUserPref(DID_SEE_ABOUT_WELCOME_PREF); Services.prefs.clearUserPref(DID_SEE_ABOUT_WELCOME_PREF);
}); });

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

@ -5,10 +5,10 @@ import {
} from "content-src/aboutwelcome/components/MultiStageAboutWelcome"; } from "content-src/aboutwelcome/components/MultiStageAboutWelcome";
import React from "react"; import React from "react";
import { shallow, mount } from "enzyme"; import { shallow, mount } from "enzyme";
import { import { AboutWelcomeDefaults } from "aboutwelcome/lib/AboutWelcomeDefaults.jsm";
DEFAULT_WELCOME_CONTENT, import { AboutWelcomeUtils } from "content-src/lib/aboutwelcome-utils";
AboutWelcomeUtils,
} from "content-src/lib/aboutwelcome-utils"; const DEFAULT_WELCOME_CONTENT = AboutWelcomeDefaults.getDefaults();
describe("MultiStageAboutWelcome module", () => { describe("MultiStageAboutWelcome module", () => {
let globals; let globals;

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

@ -4,10 +4,16 @@
"use strict"; "use strict";
const { AboutWelcomeChild } = ChromeUtils.import( const { AboutWelcomeDefaults } = ChromeUtils.import(
"resource:///actors/AboutWelcomeChild.jsm" "resource://activity-stream/aboutwelcome/lib/AboutWelcomeDefaults.jsm"
); );
const { sinon } = ChromeUtils.import("resource://testing-common/Sinon.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 = { const TEST_ATTRIBUTION_DATA = {
source: "addons.mozilla.org", source: "addons.mozilla.org",
@ -17,22 +23,32 @@ const TEST_ATTRIBUTION_DATA = {
}; };
add_task(async function test_handleAddonInfoNotFound() { add_task(async function test_handleAddonInfoNotFound() {
let AWChild = new AboutWelcomeChild(); let sandbox = sinon.createSandbox();
const stub = sinon.stub(AWChild, "getAddonInfo").resolves(null); const stub = sandbox.stub(AttributionCode, "getAttrDataAsync").resolves(null);
let result = await AWChild.formatAttributionData(TEST_ATTRIBUTION_DATA); let result = await AboutWelcomeDefaults.getAttributionContent();
equal(stub.callCount, 1, "Call was made"); 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() { add_task(async function test_formatAttributionData() {
let AWChild = new AboutWelcomeChild(); let sandbox = sinon.createSandbox();
const TEST_ADDON_INFO = { const TEST_ADDON_INFO = {
sourceURI: { scheme: "https", spec: "https://test.xpi" },
name: "Test Add-on", name: "Test Add-on",
url: "https://test.xpi", icons: { "64": "http://test.svg" },
iconURL: "http://test.svg",
}; };
sinon.stub(AWChild, "getAddonInfo").resolves(TEST_ADDON_INFO); sandbox
let result = await AWChild.formatAttributionData(TEST_ATTRIBUTION_DATA); .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.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; static MANIFEST = MANIFEST;
constructor(featureId, manifest) { constructor(featureId, manifest) {
this.featureId = featureId; this.featureId = featureId;
this.defaultPrefValues = {}; this.prefGetters = {};
this.manifest = manifest || ExperimentFeature.MANIFEST[featureId]; this.manifest = manifest || ExperimentFeature.MANIFEST[featureId];
if (!this.manifest) { if (!this.manifest) {
Cu.reportError( Cu.reportError(
@ -353,7 +353,7 @@ class ExperimentFeature {
const { type, fallbackPref } = variables[key]; const { type, fallbackPref } = variables[key];
if (fallbackPref) { if (fallbackPref) {
XPCOMUtils.defineLazyPreferenceGetter( XPCOMUtils.defineLazyPreferenceGetter(
this.defaultPrefValues, this.prefGetters,
key, key,
fallbackPref, fallbackPref,
null, 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() { ready() {
return ExperimentAPI.ready(); return ExperimentAPI.ready();
} }
@ -405,18 +421,18 @@ class ExperimentFeature {
* @param {{sendExposureEvent: boolean, defaultValue?: any}} options * @param {{sendExposureEvent: boolean, defaultValue?: any}} options
* @returns {obj} The feature value * @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({ const branch = ExperimentAPI.activateBranch({
featureId: this.featureId, featureId: this.featureId,
sendExposureEvent, sendExposureEvent,
}); });
if (branch?.feature?.value) { if (branch?.feature?.value) {
return branch.feature.value; return { ...branch.feature.value, ...userPrefs };
} }
return Object.keys(this.defaultPrefValues).length return this.prefGetters;
? this.defaultPrefValues
: defaultValue;
} }
recordExposureEvent() { recordExposureEvent() {
@ -442,11 +458,12 @@ class ExperimentFeature {
featureId: this.featureId, featureId: this.featureId,
}), }),
fallbackPrefs: fallbackPrefs:
this.defaultPrefValues && this.prefGetters &&
Object.keys(this.defaultPrefValues).map(prefName => [ Object.keys(this.prefGetters).map(prefName => [
prefName, prefName,
this.defaultPrefValues[prefName], this.prefGetters[prefName],
]), ]),
userPrefs: this._getUserPrefsValues(),
}; };
} }
} }

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

@ -67,8 +67,7 @@ Defaults values inline:
```jsx ```jsx
feature.isEnabled({ defaultValue: true }); feature.isEnabled({ defaultValue: true });
// Default values work here too const { skipFocus } = feature.getValue() || {};
const { skipFocus } = feature.getValue({ defaultValue: { skipFocus: false } });
``` ```
Listen to changes: Listen to changes:

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

@ -37,6 +37,11 @@ async function setupForExperimentFeature() {
return { sandbox, manager }; return { sandbox, manager };
} }
function setDefaultBranch(pref, value) {
let branch = Services.prefs.getDefaultBranch("");
branch.setStringPref(pref, value);
}
const TEST_FALLBACK_PREF = "testprefbranch.config"; const TEST_FALLBACK_PREF = "testprefbranch.config";
const FAKE_FEATURE_MANIFEST = { const FAKE_FEATURE_MANIFEST = {
enabledFallbackPref: "testprefbranch.enabled", enabledFallbackPref: "testprefbranch.enabled",
@ -106,14 +111,6 @@ add_task(async function test_ExperimentFeature_getValue() {
const featureInstance = new ExperimentFeature("foo", FAKE_FEATURE_MANIFEST); 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}`); Services.prefs.setStringPref(TEST_FALLBACK_PREF, `{"bar": 123}`);
Assert.deepEqual( Assert.deepEqual(
@ -145,7 +142,7 @@ add_task(
manager.store.addExperiment(expected); manager.store.addExperiment(expected);
Services.prefs.setStringPref(TEST_FALLBACK_PREF, `{"bar": 123}`); setDefaultBranch(TEST_FALLBACK_PREF, `{"bar": 123}`);
Assert.deepEqual( Assert.deepEqual(
featureInstance.getValue(), 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_SharedDataMap.js]
[test_ExperimentAPI.js] [test_ExperimentAPI.js]
[test_ExperimentAPI_ExperimentFeature.js] [test_ExperimentAPI_ExperimentFeature.js]
[test_ExperimentAPI_ExperimentFeature_getValue.js]
[test_RemoteSettingsExperimentLoader.js] [test_RemoteSettingsExperimentLoader.js]
[test_RemoteSettingsExperimentLoader_updateRecipes.js] [test_RemoteSettingsExperimentLoader_updateRecipes.js]