зеркало из https://github.com/mozilla/gecko-dev.git
Backed out 2 changesets (bug 1755519) for causing node test failures (About:welcome bundle out of date). CLOSED TREE
Backed out changeset 67e6561012ad (bug 1755519) Backed out changeset 873d83eaa24e (bug 1755519)
This commit is contained in:
Родитель
0f25064139
Коммит
51fd8df2e2
|
@ -2174,8 +2174,6 @@ pref("app.normandy.onsync_skew_sec", 600);
|
||||||
// live reloading when switching between LTR and RTL languages.
|
// live reloading when switching between LTR and RTL languages.
|
||||||
pref("intl.multilingual.liveReload", false);
|
pref("intl.multilingual.liveReload", false);
|
||||||
pref("intl.multilingual.liveReloadBidirectional", false);
|
pref("intl.multilingual.liveReloadBidirectional", false);
|
||||||
// Suggest to change the language on about:welcome when there is a mismatch with the OS.
|
|
||||||
pref("intl.multilingual.aboutWelcome.languageMismatchEnabled", false);
|
|
||||||
|
|
||||||
|
|
||||||
// Simulate conditions that will happen when the browser
|
// Simulate conditions that will happen when the browser
|
||||||
|
|
|
@ -162,22 +162,6 @@ class AboutWelcomeChild extends JSWindowActorChild {
|
||||||
Cu.exportFunction(this.AWFinish.bind(this), window, {
|
Cu.exportFunction(this.AWFinish.bind(this), window, {
|
||||||
defineAs: "AWFinish",
|
defineAs: "AWFinish",
|
||||||
});
|
});
|
||||||
|
|
||||||
Cu.exportFunction(this.AWEnsureLangPackInstalled.bind(this), window, {
|
|
||||||
defineAs: "AWEnsureLangPackInstalled",
|
|
||||||
});
|
|
||||||
|
|
||||||
Cu.exportFunction(
|
|
||||||
this.AWNegotiateLangPackForLanguageMismatch.bind(this),
|
|
||||||
window,
|
|
||||||
{
|
|
||||||
defineAs: "AWNegotiateLangPackForLanguageMismatch",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
Cu.exportFunction(this.AWSetRequestedLocales.bind(this), window, {
|
|
||||||
defineAs: "AWSetRequestedLocales",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -189,20 +173,6 @@ class AboutWelcomeChild extends JSWindowActorChild {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Clones the result of the query into the content window.
|
|
||||||
*/
|
|
||||||
sendQueryAndCloneForContent(...sendQueryArgs) {
|
|
||||||
return this.wrapPromise(
|
|
||||||
(async () => {
|
|
||||||
return Cu.cloneInto(
|
|
||||||
await this.sendQuery(...sendQueryArgs),
|
|
||||||
this.contentWindow
|
|
||||||
);
|
|
||||||
})()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
AWSelectTheme(data) {
|
AWSelectTheme(data) {
|
||||||
return this.wrapPromise(
|
return this.wrapPromise(
|
||||||
this.sendQuery("AWPage:SELECT_THEME", data.toUpperCase())
|
this.sendQuery("AWPage:SELECT_THEME", data.toUpperCase())
|
||||||
|
@ -236,11 +206,6 @@ class AboutWelcomeChild extends JSWindowActorChild {
|
||||||
let featureConfig = NimbusFeatures.aboutwelcome.getAllVariables();
|
let featureConfig = NimbusFeatures.aboutwelcome.getAllVariables();
|
||||||
featureConfig.needDefault = await this.sendQuery("AWPage:NEED_DEFAULT");
|
featureConfig.needDefault = await this.sendQuery("AWPage:NEED_DEFAULT");
|
||||||
featureConfig.needPin = await this.sendQuery("AWPage:DOES_APP_NEED_PIN");
|
featureConfig.needPin = await this.sendQuery("AWPage:DOES_APP_NEED_PIN");
|
||||||
if (featureConfig.languageMismatchEnabled) {
|
|
||||||
featureConfig.appAndSystemLocaleInfo = await this.sendQuery(
|
|
||||||
"AWPage:GET_APP_AND_SYSTEM_LOCALE_INFO"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let defaults = AboutWelcomeDefaults.getDefaults();
|
let defaults = AboutWelcomeDefaults.getDefaults();
|
||||||
// FeatureConfig (from prefs or experiments) has higher precendence
|
// FeatureConfig (from prefs or experiments) has higher precendence
|
||||||
// to defaults. But the `screens` property isn't defined we shouldn't
|
// to defaults. But the `screens` property isn't defined we shouldn't
|
||||||
|
@ -312,27 +277,6 @@ class AboutWelcomeChild extends JSWindowActorChild {
|
||||||
this.contentWindow.location.href = "about:home";
|
this.contentWindow.location.href = "about:home";
|
||||||
}
|
}
|
||||||
|
|
||||||
AWEnsureLangPackInstalled(langPack) {
|
|
||||||
return this.sendQueryAndCloneForContent(
|
|
||||||
"AWPage:ENSURE_LANG_PACK_INSTALLED",
|
|
||||||
langPack
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
AWSetRequestedLocales(requestSystemLocales) {
|
|
||||||
return this.sendQueryAndCloneForContent(
|
|
||||||
"AWPage:SET_REQUESTED_LOCALES",
|
|
||||||
requestSystemLocales
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
AWNegotiateLangPackForLanguageMismatch(appAndSystemLocaleInfo) {
|
|
||||||
return this.sendQueryAndCloneForContent(
|
|
||||||
"AWPage:NEGOTIATE_LANGPACK",
|
|
||||||
appAndSystemLocaleInfo
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {{type: string, detail?: any}} event
|
* @param {{type: string, detail?: any}} event
|
||||||
* @override
|
* @override
|
||||||
|
|
|
@ -25,7 +25,6 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
||||||
PromiseUtils: "resource://gre/modules/PromiseUtils.jsm",
|
PromiseUtils: "resource://gre/modules/PromiseUtils.jsm",
|
||||||
Region: "resource://gre/modules/Region.jsm",
|
Region: "resource://gre/modules/Region.jsm",
|
||||||
ShellService: "resource:///modules/ShellService.jsm",
|
ShellService: "resource:///modules/ShellService.jsm",
|
||||||
LangPackMatcher: "resource://gre/modules/LangPackMatcher.jsm",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
XPCOMUtils.defineLazyGetter(this, "log", () => {
|
XPCOMUtils.defineLazyGetter(this, "log", () => {
|
||||||
|
@ -305,14 +304,6 @@ class AboutWelcomeParent extends JSWindowActorParent {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
case "AWPage:GET_APP_AND_SYSTEM_LOCALE_INFO":
|
|
||||||
return LangPackMatcher.getAppAndSystemLocaleInfo();
|
|
||||||
case "AWPage:NEGOTIATE_LANGPACK":
|
|
||||||
return LangPackMatcher.negotiateLangPackForLanguageMismatch(data);
|
|
||||||
case "AWPage:ENSURE_LANG_PACK_INSTALLED":
|
|
||||||
return LangPackMatcher.ensureLangPackInstalled(data);
|
|
||||||
case "AWPage:SET_REQUESTED_LOCALES":
|
|
||||||
return LangPackMatcher.setRequestedAppLocales(data);
|
|
||||||
default:
|
default:
|
||||||
log.debug(`Unexpected event ${type} was not handled.`);
|
log.debug(`Unexpected event ${type} was not handled.`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,7 +101,7 @@ __webpack_require__.r(__webpack_exports__);
|
||||||
/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2);
|
/* 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 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__(11);
|
/* harmony import */ var _components_ReturnToAMO__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(10);
|
||||||
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
|
||||||
|
@ -191,8 +191,7 @@ class AboutWelcome extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComp
|
||||||
metricsFlowUri: this.state.metricsFlowUri,
|
metricsFlowUri: this.state.metricsFlowUri,
|
||||||
utm_term: props.UTMTerm,
|
utm_term: props.UTMTerm,
|
||||||
transitions: props.transitions,
|
transitions: props.transitions,
|
||||||
backdrop: props.backdrop,
|
backdrop: props.backdrop
|
||||||
appAndSystemLocaleInfo: props.appAndSystemLocaleInfo
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,8 +276,7 @@ __webpack_require__.r(__webpack_exports__);
|
||||||
/* harmony import */ var _MSLocalized__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4);
|
/* harmony import */ var _MSLocalized__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4);
|
||||||
/* harmony import */ var _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(5);
|
/* harmony import */ var _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(5);
|
||||||
/* harmony import */ var _MultiStageProtonScreen__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6);
|
/* harmony import */ var _MultiStageProtonScreen__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6);
|
||||||
/* harmony import */ var _LanguageSwitcher__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(9);
|
/* harmony import */ var _asrouter_templates_FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(9);
|
||||||
/* harmony import */ var _asrouter_templates_FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(10);
|
|
||||||
/* 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/. */
|
||||||
|
@ -286,18 +284,14 @@ __webpack_require__.r(__webpack_exports__);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Amount of milliseconds for all transitions to complete (including delays).
|
// Amount of milliseconds for all transitions to complete (including delays).
|
||||||
|
|
||||||
const TRANSITION_OUT_TIME = 1000;
|
const TRANSITION_OUT_TIME = 1000;
|
||||||
const MultiStageAboutWelcome = props => {
|
const MultiStageAboutWelcome = props => {
|
||||||
let {
|
|
||||||
screens
|
|
||||||
} = props;
|
|
||||||
const [index, setScreenIndex] = Object(react__WEBPACK_IMPORTED_MODULE_0__["useState"])(0);
|
const [index, setScreenIndex] = Object(react__WEBPACK_IMPORTED_MODULE_0__["useState"])(0);
|
||||||
Object(react__WEBPACK_IMPORTED_MODULE_0__["useEffect"])(() => {
|
Object(react__WEBPACK_IMPORTED_MODULE_0__["useEffect"])(() => {
|
||||||
// Send impression ping when respective screen first renders
|
// Send impression ping when respective screen first renders
|
||||||
screens.forEach((screen, order) => {
|
props.screens.forEach((screen, order) => {
|
||||||
if (index === order) {
|
if (index === order) {
|
||||||
_lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_2__["AboutWelcomeUtils"].sendImpressionTelemetry(`${props.message_id}_${order}_${screen.id}`);
|
_lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_2__["AboutWelcomeUtils"].sendImpressionTelemetry(`${props.message_id}_${order}_${screen.id}`);
|
||||||
}
|
}
|
||||||
|
@ -313,7 +307,7 @@ const MultiStageAboutWelcome = props => {
|
||||||
// button from about:home
|
// button from about:home
|
||||||
const handler = ({
|
const handler = ({
|
||||||
state
|
state
|
||||||
}) => setScreenIndex(Math.min(state, screens.length - 1)); // Handle page load, e.g., going back to about:welcome from about:home
|
}) => setScreenIndex(Math.min(state, props.screens.length - 1)); // Handle page load, e.g., going back to about:welcome from about:home
|
||||||
|
|
||||||
|
|
||||||
handler(window.history); // Watch for browser back/forward button navigation events
|
handler(window.history); // Watch for browser back/forward button navigation events
|
||||||
|
@ -351,7 +345,7 @@ const MultiStageAboutWelcome = props => {
|
||||||
setTransition(props.transitions ? "out" : ""); // Actually move forwards after all transitions finish.
|
setTransition(props.transitions ? "out" : ""); // Actually move forwards after all transitions finish.
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (index < screens.length - 1) {
|
if (index < props.screens.length - 1) {
|
||||||
setTransition(props.transitions ? "in" : "");
|
setTransition(props.transitions ? "in" : "");
|
||||||
setScreenIndex(prevState => prevState + 1);
|
setScreenIndex(prevState => prevState + 1);
|
||||||
} else {
|
} else {
|
||||||
|
@ -406,24 +400,18 @@ const MultiStageAboutWelcome = props => {
|
||||||
})();
|
})();
|
||||||
}, [useImportable, region]);
|
}, [useImportable, region]);
|
||||||
const centeredScreens = props.screens.filter(s => s.content.position !== "corner");
|
const centeredScreens = props.screens.filter(s => s.content.position !== "corner");
|
||||||
const {
|
|
||||||
negotiatedLanguage,
|
|
||||||
langPackInstallPhase,
|
|
||||||
languageFilteredScreens
|
|
||||||
} = Object(_LanguageSwitcher__WEBPACK_IMPORTED_MODULE_4__["useLanguageSwitcher"])(props.appAndSystemLocaleInfo, screens, index, setScreenIndex);
|
|
||||||
screens = languageFilteredScreens;
|
|
||||||
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react__WEBPACK_IMPORTED_MODULE_0___default.a.Fragment, null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
|
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react__WEBPACK_IMPORTED_MODULE_0___default.a.Fragment, null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
|
||||||
className: `outer-wrapper onboardingContainer proton transition-${transition}`,
|
className: `outer-wrapper onboardingContainer proton transition-${transition}`,
|
||||||
style: props.backdrop ? {
|
style: props.backdrop ? {
|
||||||
background: props.backdrop
|
background: props.backdrop
|
||||||
} : {}
|
} : {}
|
||||||
}, screens.map((screen, order) => {
|
}, props.screens.map((screen, order) => {
|
||||||
const isFirstCenteredScreen = screen.content.position !== "corner" && screen.order === centeredScreens[0].order;
|
const isFirstCenteredScreen = screen.content.position !== "corner" && screen.order === centeredScreens[0].order;
|
||||||
const isLastCenteredScreen = screen.content.position !== "corner" && screen.order === centeredScreens[centeredScreens.length - 1].order;
|
const isLastCenteredScreen = screen.content.position !== "corner" && screen.order === centeredScreens[centeredScreens.length - 1].order;
|
||||||
return index === order ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(WelcomeScreen, {
|
return index === order ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(WelcomeScreen, {
|
||||||
key: screen.id + order,
|
key: screen.id + order,
|
||||||
id: screen.id,
|
id: screen.id,
|
||||||
totalNumberOfScreens: screens.length,
|
totalNumberOfScreens: props.screens.length,
|
||||||
isFirstCenteredScreen: isFirstCenteredScreen,
|
isFirstCenteredScreen: isFirstCenteredScreen,
|
||||||
isLastCenteredScreen: isLastCenteredScreen,
|
isLastCenteredScreen: isLastCenteredScreen,
|
||||||
order: order,
|
order: order,
|
||||||
|
@ -436,9 +424,7 @@ const MultiStageAboutWelcome = props => {
|
||||||
activeTheme: activeTheme,
|
activeTheme: activeTheme,
|
||||||
initialTheme: initialTheme,
|
initialTheme: initialTheme,
|
||||||
setActiveTheme: setActiveTheme,
|
setActiveTheme: setActiveTheme,
|
||||||
autoAdvance: screen.auto_advance,
|
autoAdvance: screen.auto_advance
|
||||||
negotiatedLanguage: negotiatedLanguage,
|
|
||||||
langPackInstallPhase: langPackInstallPhase
|
|
||||||
}) : null;
|
}) : null;
|
||||||
})));
|
})));
|
||||||
};
|
};
|
||||||
|
@ -482,7 +468,7 @@ class WelcomeScreen extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureCom
|
||||||
} = action;
|
} = action;
|
||||||
|
|
||||||
if (type === "SHOW_FIREFOX_ACCOUNTS") {
|
if (type === "SHOW_FIREFOX_ACCOUNTS") {
|
||||||
let params = { ..._asrouter_templates_FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_5__["BASE_PARAMS"],
|
let params = { ..._asrouter_templates_FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_4__["BASE_PARAMS"],
|
||||||
utm_term: `aboutwelcome-${UTMTerm}-screen`
|
utm_term: `aboutwelcome-${UTMTerm}-screen`
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -497,7 +483,7 @@ class WelcomeScreen extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureCom
|
||||||
};
|
};
|
||||||
} else if (type === "OPEN_URL") {
|
} else if (type === "OPEN_URL") {
|
||||||
let url = new URL(data.args);
|
let url = new URL(data.args);
|
||||||
Object(_asrouter_templates_FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_5__["addUtmParams"])(url, `aboutwelcome-${UTMTerm}-screen`);
|
Object(_asrouter_templates_FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_4__["addUtmParams"])(url, `aboutwelcome-${UTMTerm}-screen`);
|
||||||
|
|
||||||
if (action.addFlowParams && flowParams) {
|
if (action.addFlowParams && flowParams) {
|
||||||
url.searchParams.append("device_id", flowParams.deviceId);
|
url.searchParams.append("device_id", flowParams.deviceId);
|
||||||
|
@ -523,7 +509,7 @@ class WelcomeScreen extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureCom
|
||||||
let {
|
let {
|
||||||
value
|
value
|
||||||
} = event.currentTarget;
|
} = event.currentTarget;
|
||||||
let targetContent = props.content[value] || props.content.tiles || props.content.languageSwitcher;
|
let targetContent = props.content[value] || props.content.tiles;
|
||||||
|
|
||||||
if (!(targetContent && targetContent.action)) {
|
if (!(targetContent && targetContent.action)) {
|
||||||
return;
|
return;
|
||||||
|
@ -565,11 +551,7 @@ class WelcomeScreen extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureCom
|
||||||
order: this.props.order,
|
order: this.props.order,
|
||||||
activeTheme: this.props.activeTheme,
|
activeTheme: this.props.activeTheme,
|
||||||
totalNumberOfScreens: this.props.totalNumberOfScreens - 1,
|
totalNumberOfScreens: this.props.totalNumberOfScreens - 1,
|
||||||
appAndSystemLocaleInfo: this.props.appAndSystemLocaleInfo,
|
|
||||||
negotiatedLanguage: this.props.negotiatedLanguage,
|
|
||||||
langPackInstallPhase: this.props.langPackInstallPhase,
|
|
||||||
handleAction: this.handleAction,
|
handleAction: this.handleAction,
|
||||||
messageId: this.props.messageId,
|
|
||||||
isFirstCenteredScreen: this.props.isFirstCenteredScreen,
|
isFirstCenteredScreen: this.props.isFirstCenteredScreen,
|
||||||
isLastCenteredScreen: this.props.isLastCenteredScreen,
|
isLastCenteredScreen: this.props.isLastCenteredScreen,
|
||||||
autoAdvance: this.props.autoAdvance
|
autoAdvance: this.props.autoAdvance
|
||||||
|
@ -790,7 +772,6 @@ __webpack_require__.r(__webpack_exports__);
|
||||||
/* harmony import */ var _Colorways__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(7);
|
/* harmony import */ var _Colorways__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(7);
|
||||||
/* harmony import */ var _Themes__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(8);
|
/* harmony import */ var _Themes__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(8);
|
||||||
/* harmony import */ var _MultiStageAboutWelcome__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(3);
|
/* harmony import */ var _MultiStageAboutWelcome__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(3);
|
||||||
/* harmony import */ var _LanguageSwitcher__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(9);
|
|
||||||
/* 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/. */
|
||||||
|
@ -799,7 +780,6 @@ __webpack_require__.r(__webpack_exports__);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const MultiStageProtonScreen = props => {
|
const MultiStageProtonScreen = props => {
|
||||||
const {
|
const {
|
||||||
autoAdvance,
|
autoAdvance,
|
||||||
|
@ -832,10 +812,7 @@ const MultiStageProtonScreen = props => {
|
||||||
autoAdvance: props.autoAdvance,
|
autoAdvance: props.autoAdvance,
|
||||||
isRtamo: props.isRtamo,
|
isRtamo: props.isRtamo,
|
||||||
isTheme: props.isTheme,
|
isTheme: props.isTheme,
|
||||||
iconURL: props.iconURL,
|
iconURL: props.iconURL
|
||||||
messageId: props.messageId,
|
|
||||||
negotiatedLanguage: props.negotiatedLanguage,
|
|
||||||
langPackInstallPhase: props.langPackInstallPhase
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
class ProtonScreen extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComponent {
|
class ProtonScreen extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComponent {
|
||||||
|
@ -892,19 +869,7 @@ class ProtonScreen extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComp
|
||||||
}) : null);
|
}) : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderLanguageSwitcher() {
|
|
||||||
return this.props.content.languageSwitcher ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_LanguageSwitcher__WEBPACK_IMPORTED_MODULE_5__["LanguageSwitcher"], {
|
|
||||||
content: this.props.content,
|
|
||||||
handleAction: this.props.handleAction,
|
|
||||||
negotiatedLanguage: this.props.negotiatedLanguage,
|
|
||||||
langPackInstallPhase: this.props.langPackInstallPhase,
|
|
||||||
messageId: this.props.messageId
|
|
||||||
}) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
var _this$props$appAndSys, _content$primary_butt;
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
autoAdvance,
|
autoAdvance,
|
||||||
content,
|
content,
|
||||||
|
@ -975,15 +940,13 @@ class ProtonScreen extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComp
|
||||||
text: content.subtitle
|
text: content.subtitle
|
||||||
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("h2", {
|
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("h2", {
|
||||||
"data-l10n-args": JSON.stringify({
|
"data-l10n-args": JSON.stringify({
|
||||||
"addon-name": this.props.addonName,
|
"addon-name": this.props.addonName
|
||||||
...((_this$props$appAndSys = this.props.appAndSystemLocaleInfo) === null || _this$props$appAndSys === void 0 ? void 0 : _this$props$appAndSys.displayNames)
|
|
||||||
})
|
})
|
||||||
})) : null), this.renderContentTiles(), this.renderLanguageSwitcher(), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__["Localized"], {
|
})) : null), this.renderContentTiles(), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__["Localized"], {
|
||||||
text: content.primary_button ? content.primary_button.label : null
|
text: content.primary_button ? content.primary_button.label : null
|
||||||
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("button", {
|
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("button", {
|
||||||
className: "primary",
|
className: "primary",
|
||||||
value: "primary_button",
|
value: "primary_button",
|
||||||
disabled: ((_content$primary_butt = content.primary_button) === null || _content$primary_butt === void 0 ? void 0 : _content$primary_butt.disabled) === true,
|
|
||||||
onClick: this.props.handleAction
|
onClick: this.props.handleAction
|
||||||
})), content.secondary_button ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_MultiStageAboutWelcome__WEBPACK_IMPORTED_MODULE_4__["SecondaryCTA"], {
|
})), content.secondary_button ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_MultiStageAboutWelcome__WEBPACK_IMPORTED_MODULE_4__["SecondaryCTA"], {
|
||||||
content: content,
|
content: content,
|
||||||
|
@ -1251,259 +1214,6 @@ const Themes = props => {
|
||||||
/* 9 */
|
/* 9 */
|
||||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||||
|
|
||||||
"use strict";
|
|
||||||
__webpack_require__.r(__webpack_exports__);
|
|
||||||
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "useLanguageSwitcher", function() { return useLanguageSwitcher; });
|
|
||||||
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "LanguageSwitcher", function() { return LanguageSwitcher; });
|
|
||||||
/* 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 _MSLocalized__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4);
|
|
||||||
/* harmony import */ var _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(5);
|
|
||||||
/* 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/. */
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The language switcher implements a hook that should be placed at a higher level
|
|
||||||
* than the actual language switcher component, as it needs to preemptively fetch
|
|
||||||
* and install langpacks for the user if there is a language mismatch screen.
|
|
||||||
*/
|
|
||||||
|
|
||||||
function useLanguageSwitcher(appAndSystemLocaleInfo, screens, screenIndex, setScreenIndex) {
|
|
||||||
const languageMismatchScreenIndex = screens.findIndex(({
|
|
||||||
id
|
|
||||||
}) => id === "AW_LANGUAGE_MISMATCH");
|
|
||||||
const screen = screens[languageMismatchScreenIndex]; // If there is a mismatch, then Firefox can negotiate a better langpack to offer
|
|
||||||
// the user.
|
|
||||||
|
|
||||||
const [negotiatedLanguage, setNegotiatedLanguage] = Object(react__WEBPACK_IMPORTED_MODULE_0__["useState"])(null);
|
|
||||||
Object(react__WEBPACK_IMPORTED_MODULE_0__["useEffect"])(function getNegotiatedLanguage() {
|
|
||||||
if (!appAndSystemLocaleInfo) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (appAndSystemLocaleInfo.matchType !== "language-mismatch") {
|
|
||||||
// There is no language mismatch, so there is no need to negotiate a langpack.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
const langPack = await window.AWNegotiateLangPackForLanguageMismatch(appAndSystemLocaleInfo);
|
|
||||||
|
|
||||||
if (langPack) {
|
|
||||||
// Convert the BCP 47 identifiers into the proper display names.
|
|
||||||
// e.g. "fr-CA" -> "Canadian French".
|
|
||||||
const displayNames = new Intl.DisplayNames(appAndSystemLocaleInfo.appLocaleRaw, {
|
|
||||||
type: "language"
|
|
||||||
});
|
|
||||||
setNegotiatedLanguage({
|
|
||||||
displayName: displayNames.of(langPack.target_locale),
|
|
||||||
langPack,
|
|
||||||
requestSystemLocales: [langPack.target_locale, appAndSystemLocaleInfo.appLocaleRaw]
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setNegotiatedLanguage({
|
|
||||||
displayName: null,
|
|
||||||
langPack: null,
|
|
||||||
requestSystemLocales: null
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}, [appAndSystemLocaleInfo]);
|
|
||||||
/**
|
|
||||||
* @type {
|
|
||||||
* "before-installation"
|
|
||||||
* | "installing"
|
|
||||||
* | "installed"
|
|
||||||
* | "installation-error"
|
|
||||||
* | "none-available"
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
|
|
||||||
const [langPackInstallPhase, setLangPackInstallPhase] = Object(react__WEBPACK_IMPORTED_MODULE_0__["useState"])("before-installation");
|
|
||||||
Object(react__WEBPACK_IMPORTED_MODULE_0__["useEffect"])(function ensureLangPackInstalled() {
|
|
||||||
if (!negotiatedLanguage) {
|
|
||||||
// There are no negotiated languages to download yet.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setLangPackInstallPhase("installing");
|
|
||||||
window.AWEnsureLangPackInstalled(negotiatedLanguage.langPack).then(() => {
|
|
||||||
setLangPackInstallPhase("installed");
|
|
||||||
}, error => {
|
|
||||||
console.error(error);
|
|
||||||
setLangPackInstallPhase("installation-error");
|
|
||||||
});
|
|
||||||
}, [negotiatedLanguage]);
|
|
||||||
const shouldHideLanguageSwitcher = screen && (appAndSystemLocaleInfo === null || appAndSystemLocaleInfo === void 0 ? void 0 : appAndSystemLocaleInfo.matchType) !== "language-mismatch";
|
|
||||||
const [languageFilteredScreens, setLanguageFilteredScreens] = Object(react__WEBPACK_IMPORTED_MODULE_0__["useState"])(screens);
|
|
||||||
Object(react__WEBPACK_IMPORTED_MODULE_0__["useEffect"])(function filterScreen() {
|
|
||||||
if (shouldHideLanguageSwitcher || (negotiatedLanguage === null || negotiatedLanguage === void 0 ? void 0 : negotiatedLanguage.langPack) === null) {
|
|
||||||
if (screenIndex > languageMismatchScreenIndex) {
|
|
||||||
setScreenIndex(screenIndex - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
setLanguageFilteredScreens(screens.filter(s => s.id !== "AW_LANGUAGE_MISMATCH"));
|
|
||||||
} else {
|
|
||||||
setLanguageFilteredScreens(screens);
|
|
||||||
}
|
|
||||||
}, [screens, negotiatedLanguage]);
|
|
||||||
return {
|
|
||||||
negotiatedLanguage,
|
|
||||||
langPackInstallPhase,
|
|
||||||
languageFilteredScreens
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* The language switcher is a separate component as it needs to perform some asynchronous
|
|
||||||
* network actions such as retrieving the list of langpacks available, and downloading
|
|
||||||
* a new langpack. On a fast connection, this won't be noticeable, but on slow or unreliable
|
|
||||||
* internet this may fail for a user.
|
|
||||||
*/
|
|
||||||
|
|
||||||
function LanguageSwitcher(props) {
|
|
||||||
const {
|
|
||||||
content,
|
|
||||||
handleAction,
|
|
||||||
negotiatedLanguage,
|
|
||||||
langPackInstallPhase,
|
|
||||||
messageId
|
|
||||||
} = props;
|
|
||||||
const [isAwaitingLangpack, setIsAwaitingLangpack] = Object(react__WEBPACK_IMPORTED_MODULE_0__["useState"])(false); // Determine the status of the langpack installation.
|
|
||||||
|
|
||||||
Object(react__WEBPACK_IMPORTED_MODULE_0__["useEffect"])(() => {
|
|
||||||
if (isAwaitingLangpack && langPackInstallPhase !== "installing") {
|
|
||||||
window.AWSetRequestedLocales(negotiatedLanguage.requestSystemLocales);
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
handleAction( // Simulate the click event.
|
|
||||||
{
|
|
||||||
currentTarget: {
|
|
||||||
value: "download_complete"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [isAwaitingLangpack, langPackInstallPhase]);
|
|
||||||
console.log(`!!! render`, {
|
|
||||||
negotiatedLanguage
|
|
||||||
}); // The message args are the localized language names.
|
|
||||||
|
|
||||||
const withMessageArgs = obj => {
|
|
||||||
console.log(`!!! withMessageArgs`, {
|
|
||||||
negotiatedLanguage
|
|
||||||
});
|
|
||||||
const displayName = negotiatedLanguage === null || negotiatedLanguage === void 0 ? void 0 : negotiatedLanguage.displayName;
|
|
||||||
|
|
||||||
if (displayName) {
|
|
||||||
return { ...obj,
|
|
||||||
args: { ...obj.args,
|
|
||||||
negotiatedLanguage: displayName
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return obj;
|
|
||||||
};
|
|
||||||
|
|
||||||
let showWaitingScreen = false;
|
|
||||||
let showPreloadingScreen = false;
|
|
||||||
let showReadyScreen = false;
|
|
||||||
|
|
||||||
if (isAwaitingLangpack && langPackInstallPhase !== "installed") {
|
|
||||||
showWaitingScreen = true;
|
|
||||||
} else if (langPackInstallPhase === "before-installation") {
|
|
||||||
showPreloadingScreen = true;
|
|
||||||
} else {
|
|
||||||
showReadyScreen = true;
|
|
||||||
} // Use {display: "none"} rather than if statements to prevent layout thrashing with
|
|
||||||
// the localized text elements rendering as blank, then filling in the text.
|
|
||||||
|
|
||||||
|
|
||||||
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react__WEBPACK_IMPORTED_MODULE_0___default.a.Fragment, null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
|
|
||||||
style: {
|
|
||||||
display: showPreloadingScreen ? "block" : "none"
|
|
||||||
}
|
|
||||||
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("button", {
|
|
||||||
className: "primary",
|
|
||||||
value: "primary_button",
|
|
||||||
disabled: true,
|
|
||||||
type: "button"
|
|
||||||
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("img", {
|
|
||||||
className: "language-loader",
|
|
||||||
src: "chrome://browser/skin/tabbrowser/tab-connecting.png",
|
|
||||||
alt: ""
|
|
||||||
}), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__["Localized"], {
|
|
||||||
text: content.languageSwitcher.waiting
|
|
||||||
})), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
|
|
||||||
className: "secondary-cta"
|
|
||||||
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__["Localized"], {
|
|
||||||
text: content.languageSwitcher.skip
|
|
||||||
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("button", {
|
|
||||||
value: "decline_waiting",
|
|
||||||
type: "button",
|
|
||||||
className: "secondary text-link",
|
|
||||||
onClick: handleAction
|
|
||||||
})))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
|
|
||||||
style: {
|
|
||||||
display: showWaitingScreen ? "block" : "none"
|
|
||||||
}
|
|
||||||
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("button", {
|
|
||||||
className: "primary",
|
|
||||||
value: "primary_button",
|
|
||||||
disabled: true,
|
|
||||||
type: "button"
|
|
||||||
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("img", {
|
|
||||||
className: "language-loader",
|
|
||||||
src: "chrome://browser/skin/tabbrowser/tab-connecting.png",
|
|
||||||
alt: ""
|
|
||||||
}), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__["Localized"], {
|
|
||||||
text: withMessageArgs(content.languageSwitcher.downloading)
|
|
||||||
})), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
|
|
||||||
className: "secondary-cta"
|
|
||||||
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__["Localized"], {
|
|
||||||
text: content.languageSwitcher.cancel
|
|
||||||
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("button", {
|
|
||||||
type: "button",
|
|
||||||
className: "secondary text-link",
|
|
||||||
onClick: () => {
|
|
||||||
setIsAwaitingLangpack(false);
|
|
||||||
handleAction({
|
|
||||||
currentTarget: {
|
|
||||||
value: "cancel_waiting"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
|
|
||||||
style: {
|
|
||||||
display: showReadyScreen ? "block" : "none"
|
|
||||||
}
|
|
||||||
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__["Localized"], {
|
|
||||||
text: withMessageArgs(content.languageSwitcher.switch)
|
|
||||||
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("button", {
|
|
||||||
className: "primary",
|
|
||||||
value: "primary_button",
|
|
||||||
onClick: () => {
|
|
||||||
_lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_2__["AboutWelcomeUtils"].sendActionTelemetry(messageId, "download_langpack");
|
|
||||||
setIsAwaitingLangpack(true);
|
|
||||||
}
|
|
||||||
}))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
|
|
||||||
className: "secondary-cta"
|
|
||||||
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__["Localized"], {
|
|
||||||
text: content.languageSwitcher.not_now
|
|
||||||
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("button", {
|
|
||||||
type: "button",
|
|
||||||
className: "secondary text-link",
|
|
||||||
value: "decline",
|
|
||||||
onClick: handleAction
|
|
||||||
})))));
|
|
||||||
}
|
|
||||||
|
|
||||||
/***/ }),
|
|
||||||
/* 10 */
|
|
||||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
__webpack_require__.r(__webpack_exports__);
|
__webpack_require__.r(__webpack_exports__);
|
||||||
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "BASE_PARAMS", function() { return BASE_PARAMS; });
|
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "BASE_PARAMS", function() { return BASE_PARAMS; });
|
||||||
|
@ -1542,7 +1252,7 @@ function addUtmParams(url, utmTerm) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
/* 11 */
|
/* 10 */
|
||||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
@ -1552,7 +1262,7 @@ __webpack_require__.r(__webpack_exports__);
|
||||||
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
|
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
|
||||||
/* harmony import */ var _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(5);
|
/* harmony import */ var _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(5);
|
||||||
/* harmony import */ var _MultiStageProtonScreen__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6);
|
/* harmony import */ var _MultiStageProtonScreen__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6);
|
||||||
/* harmony import */ var _asrouter_templates_FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(10);
|
/* harmony import */ var _asrouter_templates_FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(9);
|
||||||
/* 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/. */
|
||||||
|
|
|
@ -211,20 +211,6 @@ body[lwt-newtab-brighttext] {
|
||||||
.onboardingContainer .welcomeZap .zap.long::after {
|
.onboardingContainer .welcomeZap .zap.long::after {
|
||||||
background-image: url("chrome://activity-stream/content/data/content/assets/long-zap.svg");
|
background-image: url("chrome://activity-stream/content/data/content/assets/long-zap.svg");
|
||||||
}
|
}
|
||||||
.onboardingContainer .language-loader {
|
|
||||||
filter: invert(1);
|
|
||||||
margin-inline-end: 10px;
|
|
||||||
position: relative;
|
|
||||||
top: 3px;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
margin-top: -6px;
|
|
||||||
}
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
.onboardingContainer .language-loader {
|
|
||||||
filter: invert(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onboardingContainer .tiles-theme-container {
|
.onboardingContainer .tiles-theme-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
@ -101,37 +101,9 @@ const DEFAULT_WELCOME_CONTENT = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: "AW_LANGUAGE_MISMATCH",
|
|
||||||
order: 2,
|
|
||||||
content: {
|
|
||||||
title: { string_id: "onboarding-live-language-header" },
|
|
||||||
subtitle: { string_id: "onboarding-live-language-subtitle" },
|
|
||||||
has_noodles: true,
|
|
||||||
languageSwitcher: {
|
|
||||||
switch: {
|
|
||||||
string_id: "onboarding-live-language-switch-button-label",
|
|
||||||
},
|
|
||||||
downloading: {
|
|
||||||
string_id: "onboarding-live-language-button-label-downloading",
|
|
||||||
},
|
|
||||||
cancel: {
|
|
||||||
string_id: "onboarding-live-language-secondary-cancel-download",
|
|
||||||
},
|
|
||||||
not_now: {
|
|
||||||
string_id: "onboarding-live-language-not-now-button-label",
|
|
||||||
},
|
|
||||||
waiting: { string_id: "onboarding-live-language-waiting-button" },
|
|
||||||
skip: { string_id: "onboarding-live-language-skip-button-label" },
|
|
||||||
action: {
|
|
||||||
navigate: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "AW_IMPORT_SETTINGS",
|
id: "AW_IMPORT_SETTINGS",
|
||||||
order: 3,
|
order: 2,
|
||||||
content: {
|
content: {
|
||||||
title: {
|
title: {
|
||||||
string_id: "mr1-onboarding-import-header",
|
string_id: "mr1-onboarding-import-header",
|
||||||
|
@ -163,7 +135,7 @@ const DEFAULT_WELCOME_CONTENT = {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "AW_CHOOSE_THEME",
|
id: "AW_CHOOSE_THEME",
|
||||||
order: 4,
|
order: 3,
|
||||||
content: {
|
content: {
|
||||||
title: {
|
title: {
|
||||||
string_id: "mr1-onboarding-theme-header",
|
string_id: "mr1-onboarding-theme-header",
|
||||||
|
@ -439,25 +411,6 @@ async function prepareContentForReact(content) {
|
||||||
)?.content.help_text.text;
|
)?.content.help_text.text;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (content.languageMismatchEnabled) {
|
|
||||||
const screen = content?.screens?.find(s => s.id === "AW_LANGUAGE_MISMATCH");
|
|
||||||
if (screen) {
|
|
||||||
// Add the display names for the OS and Firefox languages, like "American English".
|
|
||||||
const { appAndSystemLocaleInfo } = content;
|
|
||||||
function addMessageArgs(obj) {
|
|
||||||
for (const value of Object.values(obj)) {
|
|
||||||
if (value?.string_id) {
|
|
||||||
value.args = appAndSystemLocaleInfo.displayNames;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addMessageArgs(screen.content.languageSwitcher);
|
|
||||||
addMessageArgs(screen.content);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
removeScreens(screen => screen.id === "AW_LANGUAGE_MISMATCH");
|
|
||||||
}
|
|
||||||
|
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,6 @@ class AboutWelcome extends React.PureComponent {
|
||||||
utm_term={props.UTMTerm}
|
utm_term={props.UTMTerm}
|
||||||
transitions={props.transitions}
|
transitions={props.transitions}
|
||||||
backdrop={props.backdrop}
|
backdrop={props.backdrop}
|
||||||
appAndSystemLocaleInfo={props.appAndSystemLocaleInfo}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -230,22 +230,6 @@ body {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.language-loader {
|
|
||||||
filter: invert(1);
|
|
||||||
margin-inline-end: 10px;
|
|
||||||
position: relative;
|
|
||||||
top: 3px;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
margin-top: -6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
.language-loader {
|
|
||||||
filter: invert(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tiles-theme-container {
|
.tiles-theme-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
@ -1,277 +0,0 @@
|
||||||
/* 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/. */
|
|
||||||
|
|
||||||
import React, { useState, useEffect } from "react";
|
|
||||||
import { Localized } from "./MSLocalized";
|
|
||||||
import { AboutWelcomeUtils } from "../../lib/aboutwelcome-utils";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The language switcher implements a hook that should be placed at a higher level
|
|
||||||
* than the actual language switcher component, as it needs to preemptively fetch
|
|
||||||
* and install langpacks for the user if there is a language mismatch screen.
|
|
||||||
*/
|
|
||||||
export function useLanguageSwitcher(
|
|
||||||
appAndSystemLocaleInfo,
|
|
||||||
screens,
|
|
||||||
screenIndex,
|
|
||||||
setScreenIndex
|
|
||||||
) {
|
|
||||||
const languageMismatchScreenIndex = screens.findIndex(
|
|
||||||
({ id }) => id === "AW_LANGUAGE_MISMATCH"
|
|
||||||
);
|
|
||||||
const screen = screens[languageMismatchScreenIndex];
|
|
||||||
|
|
||||||
// If there is a mismatch, then Firefox can negotiate a better langpack to offer
|
|
||||||
// the user.
|
|
||||||
const [negotiatedLanguage, setNegotiatedLanguage] = useState(null);
|
|
||||||
useEffect(
|
|
||||||
function getNegotiatedLanguage() {
|
|
||||||
if (!appAndSystemLocaleInfo) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (appAndSystemLocaleInfo.matchType !== "language-mismatch") {
|
|
||||||
// There is no language mismatch, so there is no need to negotiate a langpack.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
const langPack = await window.AWNegotiateLangPackForLanguageMismatch(
|
|
||||||
appAndSystemLocaleInfo
|
|
||||||
);
|
|
||||||
if (langPack) {
|
|
||||||
// Convert the BCP 47 identifiers into the proper display names.
|
|
||||||
// e.g. "fr-CA" -> "Canadian French".
|
|
||||||
const displayNames = new Intl.DisplayNames(
|
|
||||||
appAndSystemLocaleInfo.appLocaleRaw,
|
|
||||||
{ type: "language" }
|
|
||||||
);
|
|
||||||
|
|
||||||
setNegotiatedLanguage({
|
|
||||||
displayName: displayNames.of(langPack.target_locale),
|
|
||||||
langPack,
|
|
||||||
requestSystemLocales: [
|
|
||||||
langPack.target_locale,
|
|
||||||
appAndSystemLocaleInfo.appLocaleRaw,
|
|
||||||
],
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setNegotiatedLanguage({
|
|
||||||
displayName: null,
|
|
||||||
langPack: null,
|
|
||||||
requestSystemLocales: null,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
},
|
|
||||||
[appAndSystemLocaleInfo]
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {
|
|
||||||
* "before-installation"
|
|
||||||
* | "installing"
|
|
||||||
* | "installed"
|
|
||||||
* | "installation-error"
|
|
||||||
* | "none-available"
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
const [langPackInstallPhase, setLangPackInstallPhase] = useState(
|
|
||||||
"before-installation"
|
|
||||||
);
|
|
||||||
useEffect(
|
|
||||||
function ensureLangPackInstalled() {
|
|
||||||
if (!negotiatedLanguage) {
|
|
||||||
// There are no negotiated languages to download yet.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setLangPackInstallPhase("installing");
|
|
||||||
window.AWEnsureLangPackInstalled(negotiatedLanguage.langPack).then(
|
|
||||||
() => {
|
|
||||||
setLangPackInstallPhase("installed");
|
|
||||||
},
|
|
||||||
error => {
|
|
||||||
console.error(error);
|
|
||||||
setLangPackInstallPhase("installation-error");
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[negotiatedLanguage]
|
|
||||||
);
|
|
||||||
|
|
||||||
const shouldHideLanguageSwitcher =
|
|
||||||
screen && appAndSystemLocaleInfo?.matchType !== "language-mismatch";
|
|
||||||
|
|
||||||
const [languageFilteredScreens, setLanguageFilteredScreens] = useState(
|
|
||||||
screens
|
|
||||||
);
|
|
||||||
useEffect(
|
|
||||||
function filterScreen() {
|
|
||||||
if (shouldHideLanguageSwitcher || negotiatedLanguage?.langPack === null) {
|
|
||||||
if (screenIndex > languageMismatchScreenIndex) {
|
|
||||||
setScreenIndex(screenIndex - 1);
|
|
||||||
}
|
|
||||||
setLanguageFilteredScreens(
|
|
||||||
screens.filter(s => s.id !== "AW_LANGUAGE_MISMATCH")
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
setLanguageFilteredScreens(screens);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[screens, negotiatedLanguage]
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
negotiatedLanguage,
|
|
||||||
langPackInstallPhase,
|
|
||||||
languageFilteredScreens,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The language switcher is a separate component as it needs to perform some asynchronous
|
|
||||||
* network actions such as retrieving the list of langpacks available, and downloading
|
|
||||||
* a new langpack. On a fast connection, this won't be noticeable, but on slow or unreliable
|
|
||||||
* internet this may fail for a user.
|
|
||||||
*/
|
|
||||||
export function LanguageSwitcher(props) {
|
|
||||||
const {
|
|
||||||
content,
|
|
||||||
handleAction,
|
|
||||||
negotiatedLanguage,
|
|
||||||
langPackInstallPhase,
|
|
||||||
messageId,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const [isAwaitingLangpack, setIsAwaitingLangpack] = useState(false);
|
|
||||||
|
|
||||||
// Determine the status of the langpack installation.
|
|
||||||
useEffect(() => {
|
|
||||||
if (isAwaitingLangpack && langPackInstallPhase !== "installing") {
|
|
||||||
window.AWSetRequestedLocales(negotiatedLanguage.requestSystemLocales);
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
handleAction(
|
|
||||||
// Simulate the click event.
|
|
||||||
{ currentTarget: { value: "download_complete" } }
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [isAwaitingLangpack, langPackInstallPhase]);
|
|
||||||
|
|
||||||
// The message args are the localized language names.
|
|
||||||
const withMessageArgs = obj => {
|
|
||||||
const displayName = negotiatedLanguage?.displayName;
|
|
||||||
if (displayName) {
|
|
||||||
return {
|
|
||||||
...obj,
|
|
||||||
args: {
|
|
||||||
...obj.args,
|
|
||||||
negotiatedLanguage: displayName,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
};
|
|
||||||
|
|
||||||
let showWaitingScreen = false;
|
|
||||||
let showPreloadingScreen = false;
|
|
||||||
let showReadyScreen = false;
|
|
||||||
|
|
||||||
if (isAwaitingLangpack && langPackInstallPhase !== "installed") {
|
|
||||||
showWaitingScreen = true;
|
|
||||||
} else if (langPackInstallPhase === "before-installation") {
|
|
||||||
showPreloadingScreen = true;
|
|
||||||
} else {
|
|
||||||
showReadyScreen = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use {display: "none"} rather than if statements to prevent layout thrashing with
|
|
||||||
// the localized text elements rendering as blank, then filling in the text.
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div style={{ display: showPreloadingScreen ? "block" : "none" }}>
|
|
||||||
<button
|
|
||||||
className="primary"
|
|
||||||
value="primary_button"
|
|
||||||
disabled={true}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
className="language-loader"
|
|
||||||
src="chrome://browser/skin/tabbrowser/tab-connecting.png"
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
<Localized text={content.languageSwitcher.waiting} />
|
|
||||||
</button>
|
|
||||||
<div className="secondary-cta">
|
|
||||||
<Localized text={content.languageSwitcher.skip}>
|
|
||||||
<button
|
|
||||||
value="decline_waiting"
|
|
||||||
type="button"
|
|
||||||
className="secondary text-link"
|
|
||||||
onClick={handleAction}
|
|
||||||
/>
|
|
||||||
</Localized>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style={{ display: showWaitingScreen ? "block" : "none" }}>
|
|
||||||
<button
|
|
||||||
className="primary"
|
|
||||||
value="primary_button"
|
|
||||||
disabled={true}
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
className="language-loader"
|
|
||||||
src="chrome://browser/skin/tabbrowser/tab-connecting.png"
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
<Localized
|
|
||||||
text={withMessageArgs(content.languageSwitcher.downloading)}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<div className="secondary-cta">
|
|
||||||
<Localized text={content.languageSwitcher.cancel}>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="secondary text-link"
|
|
||||||
onClick={() => {
|
|
||||||
setIsAwaitingLangpack(false);
|
|
||||||
handleAction({
|
|
||||||
currentTarget: { value: "cancel_waiting" },
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Localized>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style={{ display: showReadyScreen ? "block" : "none" }}>
|
|
||||||
<div>
|
|
||||||
<Localized text={withMessageArgs(content.languageSwitcher.switch)}>
|
|
||||||
<button
|
|
||||||
className="primary"
|
|
||||||
value="primary_button"
|
|
||||||
onClick={() => {
|
|
||||||
AboutWelcomeUtils.sendActionTelemetry(
|
|
||||||
messageId,
|
|
||||||
"download_langpack"
|
|
||||||
);
|
|
||||||
setIsAwaitingLangpack(true);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Localized>
|
|
||||||
</div>
|
|
||||||
<div className="secondary-cta">
|
|
||||||
<Localized text={content.languageSwitcher.not_now}>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="secondary text-link"
|
|
||||||
value="decline"
|
|
||||||
onClick={handleAction}
|
|
||||||
/>
|
|
||||||
</Localized>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -6,7 +6,6 @@ import React, { useState, useEffect, useRef } from "react";
|
||||||
import { Localized } from "./MSLocalized";
|
import { Localized } from "./MSLocalized";
|
||||||
import { AboutWelcomeUtils } from "../../lib/aboutwelcome-utils";
|
import { AboutWelcomeUtils } from "../../lib/aboutwelcome-utils";
|
||||||
import { MultiStageProtonScreen } from "./MultiStageProtonScreen";
|
import { MultiStageProtonScreen } from "./MultiStageProtonScreen";
|
||||||
import { useLanguageSwitcher } from "./LanguageSwitcher";
|
|
||||||
import {
|
import {
|
||||||
BASE_PARAMS,
|
BASE_PARAMS,
|
||||||
addUtmParams,
|
addUtmParams,
|
||||||
|
@ -16,12 +15,10 @@ import {
|
||||||
const TRANSITION_OUT_TIME = 1000;
|
const TRANSITION_OUT_TIME = 1000;
|
||||||
|
|
||||||
export const MultiStageAboutWelcome = props => {
|
export const MultiStageAboutWelcome = props => {
|
||||||
let { screens } = props;
|
|
||||||
|
|
||||||
const [index, setScreenIndex] = useState(0);
|
const [index, setScreenIndex] = useState(0);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Send impression ping when respective screen first renders
|
// Send impression ping when respective screen first renders
|
||||||
screens.forEach((screen, order) => {
|
props.screens.forEach((screen, order) => {
|
||||||
if (index === order) {
|
if (index === order) {
|
||||||
AboutWelcomeUtils.sendImpressionTelemetry(
|
AboutWelcomeUtils.sendImpressionTelemetry(
|
||||||
`${props.message_id}_${order}_${screen.id}`
|
`${props.message_id}_${order}_${screen.id}`
|
||||||
|
@ -40,7 +37,7 @@ export const MultiStageAboutWelcome = props => {
|
||||||
// or last screen index if a user navigates by pressing back
|
// or last screen index if a user navigates by pressing back
|
||||||
// button from about:home
|
// button from about:home
|
||||||
const handler = ({ state }) =>
|
const handler = ({ state }) =>
|
||||||
setScreenIndex(Math.min(state, screens.length - 1));
|
setScreenIndex(Math.min(state, props.screens.length - 1));
|
||||||
|
|
||||||
// Handle page load, e.g., going back to about:welcome from about:home
|
// Handle page load, e.g., going back to about:welcome from about:home
|
||||||
handler(window.history);
|
handler(window.history);
|
||||||
|
@ -84,7 +81,7 @@ export const MultiStageAboutWelcome = props => {
|
||||||
// Actually move forwards after all transitions finish.
|
// Actually move forwards after all transitions finish.
|
||||||
setTimeout(
|
setTimeout(
|
||||||
() => {
|
() => {
|
||||||
if (index < screens.length - 1) {
|
if (index < props.screens.length - 1) {
|
||||||
setTransition(props.transitions ? "in" : "");
|
setTransition(props.transitions ? "in" : "");
|
||||||
setScreenIndex(prevState => prevState + 1);
|
setScreenIndex(prevState => prevState + 1);
|
||||||
} else {
|
} else {
|
||||||
|
@ -143,26 +140,13 @@ export const MultiStageAboutWelcome = props => {
|
||||||
s => s.content.position !== "corner"
|
s => s.content.position !== "corner"
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
|
||||||
negotiatedLanguage,
|
|
||||||
langPackInstallPhase,
|
|
||||||
languageFilteredScreens,
|
|
||||||
} = useLanguageSwitcher(
|
|
||||||
props.appAndSystemLocaleInfo,
|
|
||||||
screens,
|
|
||||||
index,
|
|
||||||
setScreenIndex
|
|
||||||
);
|
|
||||||
|
|
||||||
screens = languageFilteredScreens;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<div
|
<div
|
||||||
className={`outer-wrapper onboardingContainer proton transition-${transition}`}
|
className={`outer-wrapper onboardingContainer proton transition-${transition}`}
|
||||||
style={props.backdrop ? { background: props.backdrop } : {}}
|
style={props.backdrop ? { background: props.backdrop } : {}}
|
||||||
>
|
>
|
||||||
{screens.map((screen, order) => {
|
{props.screens.map((screen, order) => {
|
||||||
const isFirstCenteredScreen =
|
const isFirstCenteredScreen =
|
||||||
screen.content.position !== "corner" &&
|
screen.content.position !== "corner" &&
|
||||||
screen.order === centeredScreens[0].order;
|
screen.order === centeredScreens[0].order;
|
||||||
|
@ -173,7 +157,7 @@ export const MultiStageAboutWelcome = props => {
|
||||||
<WelcomeScreen
|
<WelcomeScreen
|
||||||
key={screen.id + order}
|
key={screen.id + order}
|
||||||
id={screen.id}
|
id={screen.id}
|
||||||
totalNumberOfScreens={screens.length}
|
totalNumberOfScreens={props.screens.length}
|
||||||
isFirstCenteredScreen={isFirstCenteredScreen}
|
isFirstCenteredScreen={isFirstCenteredScreen}
|
||||||
isLastCenteredScreen={isLastCenteredScreen}
|
isLastCenteredScreen={isLastCenteredScreen}
|
||||||
order={order}
|
order={order}
|
||||||
|
@ -187,8 +171,6 @@ export const MultiStageAboutWelcome = props => {
|
||||||
initialTheme={initialTheme}
|
initialTheme={initialTheme}
|
||||||
setActiveTheme={setActiveTheme}
|
setActiveTheme={setActiveTheme}
|
||||||
autoAdvance={screen.auto_advance}
|
autoAdvance={screen.auto_advance}
|
||||||
negotiatedLanguage={negotiatedLanguage}
|
|
||||||
langPackInstallPhase={langPackInstallPhase}
|
|
||||||
/>
|
/>
|
||||||
) : null;
|
) : null;
|
||||||
})}
|
})}
|
||||||
|
@ -266,11 +248,7 @@ export class WelcomeScreen extends React.PureComponent {
|
||||||
async handleAction(event) {
|
async handleAction(event) {
|
||||||
let { props } = this;
|
let { props } = this;
|
||||||
let { value } = event.currentTarget;
|
let { value } = event.currentTarget;
|
||||||
let targetContent =
|
let targetContent = props.content[value] || props.content.tiles;
|
||||||
props.content[value] ||
|
|
||||||
props.content.tiles ||
|
|
||||||
props.content.languageSwitcher;
|
|
||||||
|
|
||||||
if (!(targetContent && targetContent.action)) {
|
if (!(targetContent && targetContent.action)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -317,11 +295,7 @@ export class WelcomeScreen extends React.PureComponent {
|
||||||
order={this.props.order}
|
order={this.props.order}
|
||||||
activeTheme={this.props.activeTheme}
|
activeTheme={this.props.activeTheme}
|
||||||
totalNumberOfScreens={this.props.totalNumberOfScreens - 1}
|
totalNumberOfScreens={this.props.totalNumberOfScreens - 1}
|
||||||
appAndSystemLocaleInfo={this.props.appAndSystemLocaleInfo}
|
|
||||||
negotiatedLanguage={this.props.negotiatedLanguage}
|
|
||||||
langPackInstallPhase={this.props.langPackInstallPhase}
|
|
||||||
handleAction={this.handleAction}
|
handleAction={this.handleAction}
|
||||||
messageId={this.props.messageId}
|
|
||||||
isFirstCenteredScreen={this.props.isFirstCenteredScreen}
|
isFirstCenteredScreen={this.props.isFirstCenteredScreen}
|
||||||
isLastCenteredScreen={this.props.isLastCenteredScreen}
|
isLastCenteredScreen={this.props.isLastCenteredScreen}
|
||||||
autoAdvance={this.props.autoAdvance}
|
autoAdvance={this.props.autoAdvance}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import { Localized } from "./MSLocalized";
|
||||||
import { Colorways } from "./Colorways";
|
import { Colorways } from "./Colorways";
|
||||||
import { Themes } from "./Themes";
|
import { Themes } from "./Themes";
|
||||||
import { SecondaryCTA, StepsIndicator } from "./MultiStageAboutWelcome";
|
import { SecondaryCTA, StepsIndicator } from "./MultiStageAboutWelcome";
|
||||||
import { LanguageSwitcher } from "./LanguageSwitcher";
|
|
||||||
|
|
||||||
export const MultiStageProtonScreen = props => {
|
export const MultiStageProtonScreen = props => {
|
||||||
const { autoAdvance, handleAction, order } = props;
|
const { autoAdvance, handleAction, order } = props;
|
||||||
|
@ -39,9 +38,6 @@ export const MultiStageProtonScreen = props => {
|
||||||
isRtamo={props.isRtamo}
|
isRtamo={props.isRtamo}
|
||||||
isTheme={props.isTheme}
|
isTheme={props.isTheme}
|
||||||
iconURL={props.iconURL}
|
iconURL={props.iconURL}
|
||||||
messageId={props.messageId}
|
|
||||||
negotiatedLanguage={props.negotiatedLanguage}
|
|
||||||
langPackInstallPhase={props.langPackInstallPhase}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -118,18 +114,6 @@ export class ProtonScreen extends React.PureComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderLanguageSwitcher() {
|
|
||||||
return this.props.content.languageSwitcher ? (
|
|
||||||
<LanguageSwitcher
|
|
||||||
content={this.props.content}
|
|
||||||
handleAction={this.props.handleAction}
|
|
||||||
negotiatedLanguage={this.props.negotiatedLanguage}
|
|
||||||
langPackInstallPhase={this.props.langPackInstallPhase}
|
|
||||||
messageId={this.props.messageId}
|
|
||||||
/>
|
|
||||||
) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
autoAdvance,
|
autoAdvance,
|
||||||
|
@ -217,14 +201,12 @@ export class ProtonScreen extends React.PureComponent {
|
||||||
<h2
|
<h2
|
||||||
data-l10n-args={JSON.stringify({
|
data-l10n-args={JSON.stringify({
|
||||||
"addon-name": this.props.addonName,
|
"addon-name": this.props.addonName,
|
||||||
...this.props.appAndSystemLocaleInfo?.displayNames,
|
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</Localized>
|
</Localized>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
{this.renderContentTiles()}
|
{this.renderContentTiles()}
|
||||||
{this.renderLanguageSwitcher()}
|
|
||||||
<div>
|
<div>
|
||||||
<Localized
|
<Localized
|
||||||
text={
|
text={
|
||||||
|
@ -234,7 +216,6 @@ export class ProtonScreen extends React.PureComponent {
|
||||||
<button
|
<button
|
||||||
className="primary"
|
className="primary"
|
||||||
value="primary_button"
|
value="primary_button"
|
||||||
disabled={content.primary_button?.disabled === true}
|
|
||||||
onClick={this.props.handleAction}
|
onClick={this.props.handleAction}
|
||||||
/>
|
/>
|
||||||
</Localized>
|
</Localized>
|
||||||
|
|
|
@ -188,13 +188,6 @@ module.exports = function(config) {
|
||||||
functions: 96,
|
functions: 96,
|
||||||
branches: 70,
|
branches: 70,
|
||||||
},
|
},
|
||||||
"content-src/aboutwelcome/components/LanguageSwitcher.jsx": {
|
|
||||||
// This file is covered by the mochitest: browser_aboutwelcome_multistage_languageSwitcher.js
|
|
||||||
statements: 0,
|
|
||||||
lines: 0,
|
|
||||||
functions: 0,
|
|
||||||
branches: 0,
|
|
||||||
},
|
|
||||||
"content-src/aboutwelcome/**/*.jsx": {
|
"content-src/aboutwelcome/**/*.jsx": {
|
||||||
statements: 62,
|
statements: 62,
|
||||||
lines: 60,
|
lines: 60,
|
||||||
|
|
|
@ -16,14 +16,12 @@ prefs =
|
||||||
browser.newtabpage.activity-stream.feeds.section.topstories=true
|
browser.newtabpage.activity-stream.feeds.section.topstories=true
|
||||||
browser.newtabpage.activity-stream.feeds.section.topstories.options={"provider_name":""}
|
browser.newtabpage.activity-stream.feeds.section.topstories.options={"provider_name":""}
|
||||||
messaging-system.log=all
|
messaging-system.log=all
|
||||||
intl.multilingual.aboutWelcome.languageMismatchEnabled=false
|
|
||||||
|
|
||||||
[browser_aboutwelcome_configurable_ui.js]
|
[browser_aboutwelcome_configurable_ui.js]
|
||||||
[browser_aboutwelcome_focus.js]
|
[browser_aboutwelcome_focus.js]
|
||||||
[browser_aboutwelcome_multistage_default.js]
|
[browser_aboutwelcome_multistage_default.js]
|
||||||
[browser_aboutwelcome_multistage_primary.js]
|
[browser_aboutwelcome_multistage_primary.js]
|
||||||
[browser_aboutwelcome_multistage_experimentAPI.js]
|
[browser_aboutwelcome_multistage_experimentAPI.js]
|
||||||
[browser_aboutwelcome_multistage_languageSwitcher.js]
|
|
||||||
[browser_aboutwelcome_rtamo.js]
|
[browser_aboutwelcome_rtamo.js]
|
||||||
skip-if = (os == "linux") # Test setup only implemented for OSX and Windows
|
skip-if = (os == "linux") # Test setup only implemented for OSX and Windows
|
||||||
[browser_aboutwelcome_attribution.js]
|
[browser_aboutwelcome_attribution.js]
|
||||||
|
|
|
@ -1,572 +0,0 @@
|
||||||
"use strict";
|
|
||||||
|
|
||||||
const { getAddonAndLocalAPIsMocker } = ChromeUtils.import(
|
|
||||||
"resource://testing-common/LangPackMatcherTestUtils.jsm"
|
|
||||||
);
|
|
||||||
|
|
||||||
const sandbox = sinon.createSandbox();
|
|
||||||
const mockAddonAndLocaleAPIs = getAddonAndLocalAPIsMocker(this, sandbox);
|
|
||||||
add_task(function initSandbox() {
|
|
||||||
registerCleanupFunction(() => {
|
|
||||||
sandbox.restore();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Spy specifically on the button click telemetry.
|
|
||||||
*
|
|
||||||
* The returned function flushes the spy of all of the matching button click events, and
|
|
||||||
* returns the events.
|
|
||||||
* @returns {() => TelemetryEvents[]}
|
|
||||||
*/
|
|
||||||
async function spyOnTelemetryButtonClicks(browser) {
|
|
||||||
let aboutWelcomeActor = await getAboutWelcomeParent(browser);
|
|
||||||
sandbox.spy(aboutWelcomeActor, "onContentMessage");
|
|
||||||
return () => {
|
|
||||||
const result = aboutWelcomeActor.onContentMessage
|
|
||||||
.getCalls()
|
|
||||||
.filter(
|
|
||||||
call =>
|
|
||||||
call.args[0] === "AWPage:TELEMETRY_EVENT" &&
|
|
||||||
call.args[1]?.event === "CLICK_BUTTON"
|
|
||||||
)
|
|
||||||
// The second argument is the telemetry event.
|
|
||||||
.map(call => call.args[1]);
|
|
||||||
|
|
||||||
aboutWelcomeActor.onContentMessage.resetHistory();
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function openAboutWelcome() {
|
|
||||||
await pushPrefs([
|
|
||||||
"intl.multilingual.aboutWelcome.languageMismatchEnabled",
|
|
||||||
true,
|
|
||||||
]);
|
|
||||||
await setAboutWelcomePref(true);
|
|
||||||
|
|
||||||
// Stub out the doesAppNeedPin to false so the about:welcome pages do not attempt
|
|
||||||
// to pin the app.
|
|
||||||
const { ShellService } = ChromeUtils.import(
|
|
||||||
"resource:///modules/ShellService.jsm"
|
|
||||||
);
|
|
||||||
sandbox.stub(ShellService, "doesAppNeedPin").returns(false);
|
|
||||||
|
|
||||||
info("Opening about:welcome");
|
|
||||||
let tab = await BrowserTestUtils.openNewForegroundTab(
|
|
||||||
gBrowser,
|
|
||||||
"about:welcome",
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
registerCleanupFunction(async () => {
|
|
||||||
BrowserTestUtils.removeTab(tab);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
browser: tab.linkedBrowser,
|
|
||||||
flushClickTelemetry: await spyOnTelemetryButtonClicks(tab.linkedBrowser),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function clickVisibleButton(browser, selector) {
|
|
||||||
// eslint-disable-next-line no-shadow
|
|
||||||
await ContentTask.spawn(browser, { selector }, async ({ selector }) => {
|
|
||||||
function getVisibleElement() {
|
|
||||||
for (const el of content.document.querySelectorAll(selector)) {
|
|
||||||
if (el.offsetParent !== null) {
|
|
||||||
return el;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
await ContentTaskUtils.waitForCondition(getVisibleElement, selector);
|
|
||||||
getVisibleElement().click();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test that selectors are present and visible.
|
|
||||||
*/
|
|
||||||
async function testScreenContent(
|
|
||||||
browser,
|
|
||||||
name,
|
|
||||||
expectedSelectors = [],
|
|
||||||
unexpectedSelectors = []
|
|
||||||
) {
|
|
||||||
await ContentTask.spawn(
|
|
||||||
browser,
|
|
||||||
{ expectedSelectors, name, unexpectedSelectors },
|
|
||||||
async ({
|
|
||||||
expectedSelectors: expected,
|
|
||||||
name: experimentName,
|
|
||||||
unexpectedSelectors: unexpected,
|
|
||||||
}) => {
|
|
||||||
function selectorIsVisible(selector) {
|
|
||||||
const el = content.document.querySelector(selector);
|
|
||||||
// The offsetParent will be null if element is hidden through "display: none;"
|
|
||||||
return el && el.offsetParent !== null;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let selector of expected) {
|
|
||||||
await ContentTaskUtils.waitForCondition(
|
|
||||||
() => selectorIsVisible(selector),
|
|
||||||
`Should render ${selector} in ${experimentName}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for (let selector of unexpected) {
|
|
||||||
ok(
|
|
||||||
!selectorIsVisible(selector),
|
|
||||||
`Should not render ${selector} in ${experimentName}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Report telemetry mismatches nicely.
|
|
||||||
*/
|
|
||||||
function eventsMatch(
|
|
||||||
actualEvents,
|
|
||||||
expectedEvents,
|
|
||||||
message = "Telemetry events match"
|
|
||||||
) {
|
|
||||||
if (actualEvents.length !== expectedEvents.length) {
|
|
||||||
console.error("Events do not match");
|
|
||||||
console.error("Actual: ", JSON.stringify(actualEvents, null, 2));
|
|
||||||
console.error("Expected: ", JSON.stringify(expectedEvents, null, 2));
|
|
||||||
}
|
|
||||||
for (let i = 0; i < actualEvents.length; i++) {
|
|
||||||
const actualEvent = JSON.stringify(actualEvents[i], null, 2);
|
|
||||||
const expectedEvent = JSON.stringify(expectedEvents[i], null, 2);
|
|
||||||
if (actualEvent !== expectedEvent) {
|
|
||||||
console.error("Events do not match");
|
|
||||||
dump(`Actual: ${actualEvent}`);
|
|
||||||
dump("\n");
|
|
||||||
dump(`Expected: ${expectedEvent}`);
|
|
||||||
dump("\n");
|
|
||||||
}
|
|
||||||
ok(actualEvent === expectedEvent, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const liveLanguageSwitchSelectors = [
|
|
||||||
".screen-1",
|
|
||||||
`[data-l10n-id*="onboarding-live-language"]`,
|
|
||||||
`[data-l10n-id="onboarding-live-language-header"]`,
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Accept the about:welcome offer to change the Firefox language when
|
|
||||||
* there is a mismatch between the operating system language and the Firefox
|
|
||||||
* language.
|
|
||||||
*/
|
|
||||||
add_task(async function test_aboutwelcome_languageSwitcher_accept() {
|
|
||||||
sandbox.restore();
|
|
||||||
const { resolveLangPacks, resolveInstaller } = mockAddonAndLocaleAPIs({
|
|
||||||
systemLocale: "es-ES",
|
|
||||||
appLocale: "en-US",
|
|
||||||
});
|
|
||||||
|
|
||||||
const { browser, flushClickTelemetry } = await openAboutWelcome();
|
|
||||||
|
|
||||||
info("Clicking the primary button to start the onboarding process.");
|
|
||||||
await clickVisibleButton(browser, "button.primary");
|
|
||||||
|
|
||||||
await testScreenContent(
|
|
||||||
browser,
|
|
||||||
"Live language switching (waiting for languages)",
|
|
||||||
// Expected selectors:
|
|
||||||
[
|
|
||||||
...liveLanguageSwitchSelectors,
|
|
||||||
`[data-l10n-id="onboarding-live-language-header"]`,
|
|
||||||
`button[disabled] [data-l10n-id="onboarding-live-language-waiting-button"]`,
|
|
||||||
`[data-l10n-id="onboarding-live-language-skip-button-label"]`,
|
|
||||||
],
|
|
||||||
// Unexpected selectors:
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Ignore the telemetry of the initial welcome screen.
|
|
||||||
flushClickTelemetry();
|
|
||||||
|
|
||||||
resolveLangPacks(["es-MX", "es-ES", "fr-FR"]);
|
|
||||||
|
|
||||||
await testScreenContent(
|
|
||||||
browser,
|
|
||||||
"Live language switching, asking for a language",
|
|
||||||
// Expected selectors:
|
|
||||||
[
|
|
||||||
...liveLanguageSwitchSelectors,
|
|
||||||
`[data-l10n-id="onboarding-live-language-switch-button-label"]`,
|
|
||||||
`[data-l10n-id="onboarding-live-language-not-now-button-label"]`,
|
|
||||||
],
|
|
||||||
// Unexpected selectors:
|
|
||||||
[
|
|
||||||
`button[disabled] [data-l10n-id="onboarding-live-language-waiting-button"]`,
|
|
||||||
`[data-l10n-id="onboarding-live-language-skip-button-label"]`,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
info("Clicking the primary button to view language switching page.");
|
|
||||||
await clickVisibleButton(browser, "button.primary");
|
|
||||||
|
|
||||||
await testScreenContent(
|
|
||||||
browser,
|
|
||||||
"Live language switching, waiting for langpack to download",
|
|
||||||
// Expected selectors:
|
|
||||||
[
|
|
||||||
...liveLanguageSwitchSelectors,
|
|
||||||
`[data-l10n-id="onboarding-live-language-button-label-downloading"]`,
|
|
||||||
`[data-l10n-id="onboarding-live-language-secondary-cancel-download"]`,
|
|
||||||
],
|
|
||||||
// Unexpected selectors:
|
|
||||||
[
|
|
||||||
`button[disabled] [data-l10n-id="onboarding-live-language-waiting-button"]`,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
eventsMatch(flushClickTelemetry(), [
|
|
||||||
{
|
|
||||||
event: "CLICK_BUTTON",
|
|
||||||
event_context: {
|
|
||||||
source: "download_langpack",
|
|
||||||
page: "about:welcome",
|
|
||||||
},
|
|
||||||
message_id: "DEFAULT_ABOUTWELCOME_PROTON_1_AW_LANGUAGE_MISMATCH",
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
await resolveInstaller();
|
|
||||||
|
|
||||||
await testScreenContent(
|
|
||||||
browser,
|
|
||||||
"Language selection declined",
|
|
||||||
// Expected selectors:
|
|
||||||
[`.screen-2`],
|
|
||||||
// Unexpected selectors:
|
|
||||||
liveLanguageSwitchSelectors
|
|
||||||
);
|
|
||||||
|
|
||||||
eventsMatch(flushClickTelemetry(), [
|
|
||||||
{
|
|
||||||
event: "CLICK_BUTTON",
|
|
||||||
event_context: {
|
|
||||||
source: "download_complete",
|
|
||||||
page: "about:welcome",
|
|
||||||
},
|
|
||||||
message_id: "DEFAULT_ABOUTWELCOME_PROTON_1_AW_LANGUAGE_MISMATCH",
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Accept the about:welcome offer to change the Firefox language when
|
|
||||||
* there is a mismatch between the operating system language and the Firefox
|
|
||||||
* language.
|
|
||||||
*/
|
|
||||||
add_task(async function test_aboutwelcome_languageSwitcher_accept() {
|
|
||||||
sandbox.restore();
|
|
||||||
const { resolveLangPacks, resolveInstaller } = mockAddonAndLocaleAPIs({
|
|
||||||
systemLocale: "es-ES",
|
|
||||||
appLocale: "en-US",
|
|
||||||
});
|
|
||||||
|
|
||||||
const { browser, flushClickTelemetry } = await openAboutWelcome();
|
|
||||||
|
|
||||||
info("Clicking the primary button to start the onboarding process.");
|
|
||||||
await clickVisibleButton(browser, "button.primary");
|
|
||||||
|
|
||||||
await testScreenContent(
|
|
||||||
browser,
|
|
||||||
"Live language switching (waiting for languages)",
|
|
||||||
// Expected selectors:
|
|
||||||
[
|
|
||||||
...liveLanguageSwitchSelectors,
|
|
||||||
`[data-l10n-id="onboarding-live-language-header"]`,
|
|
||||||
`button[disabled] [data-l10n-id="onboarding-live-language-waiting-button"]`,
|
|
||||||
`[data-l10n-id="onboarding-live-language-skip-button-label"]`,
|
|
||||||
],
|
|
||||||
// Unexpected selectors:
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Ignore the telemetry of the initial welcome screen.
|
|
||||||
flushClickTelemetry();
|
|
||||||
|
|
||||||
resolveLangPacks(["es-MX", "es-ES", "fr-FR"]);
|
|
||||||
|
|
||||||
await testScreenContent(
|
|
||||||
browser,
|
|
||||||
"Live language switching, asking for a language",
|
|
||||||
// Expected selectors:
|
|
||||||
[
|
|
||||||
...liveLanguageSwitchSelectors,
|
|
||||||
`[data-l10n-id="onboarding-live-language-switch-button-label"]`,
|
|
||||||
`[data-l10n-id="onboarding-live-language-not-now-button-label"]`,
|
|
||||||
],
|
|
||||||
// Unexpected selectors:
|
|
||||||
[
|
|
||||||
`button[disabled] [data-l10n-id="onboarding-live-language-waiting-button"]`,
|
|
||||||
`[data-l10n-id="onboarding-live-language-skip-button-label"]`,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
info("Clicking the primary button to view language switching page.");
|
|
||||||
await clickVisibleButton(browser, "button.primary");
|
|
||||||
|
|
||||||
await testScreenContent(
|
|
||||||
browser,
|
|
||||||
"Live language switching, waiting for langpack to download",
|
|
||||||
// Expected selectors:
|
|
||||||
[
|
|
||||||
...liveLanguageSwitchSelectors,
|
|
||||||
`[data-l10n-id="onboarding-live-language-button-label-downloading"]`,
|
|
||||||
`[data-l10n-id="onboarding-live-language-secondary-cancel-download"]`,
|
|
||||||
],
|
|
||||||
// Unexpected selectors:
|
|
||||||
[
|
|
||||||
`button[disabled] [data-l10n-id="onboarding-live-language-waiting-button"]`,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
eventsMatch(flushClickTelemetry(), [
|
|
||||||
{
|
|
||||||
event: "CLICK_BUTTON",
|
|
||||||
event_context: {
|
|
||||||
source: "download_langpack",
|
|
||||||
page: "about:welcome",
|
|
||||||
},
|
|
||||||
message_id: "DEFAULT_ABOUTWELCOME_PROTON_1_AW_LANGUAGE_MISMATCH",
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
await resolveInstaller();
|
|
||||||
|
|
||||||
await testScreenContent(
|
|
||||||
browser,
|
|
||||||
"Language selection declined",
|
|
||||||
// Expected selectors:
|
|
||||||
[`.screen-2`],
|
|
||||||
// Unexpected selectors:
|
|
||||||
liveLanguageSwitchSelectors
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test declining the about:welcome offer to change the Firefox language when
|
|
||||||
* there is a mismatch between the operating system language and the Firefox
|
|
||||||
* language.
|
|
||||||
*/
|
|
||||||
add_task(async function test_aboutwelcome_languageSwitcher_decline() {
|
|
||||||
sandbox.restore();
|
|
||||||
const { resolveLangPacks, resolveInstaller } = mockAddonAndLocaleAPIs({
|
|
||||||
systemLocale: "es-ES",
|
|
||||||
appLocale: "en-US",
|
|
||||||
});
|
|
||||||
|
|
||||||
const { browser, flushClickTelemetry } = await openAboutWelcome();
|
|
||||||
|
|
||||||
info("Clicking the primary button to view language switching page.");
|
|
||||||
await clickVisibleButton(browser, "button.primary");
|
|
||||||
|
|
||||||
await testScreenContent(
|
|
||||||
browser,
|
|
||||||
"Live language switching (waiting for languages)",
|
|
||||||
// Expected selectors:
|
|
||||||
[
|
|
||||||
...liveLanguageSwitchSelectors,
|
|
||||||
`[data-l10n-id="onboarding-live-language-header"]`,
|
|
||||||
`button[disabled] [data-l10n-id="onboarding-live-language-waiting-button"]`,
|
|
||||||
`[data-l10n-id="onboarding-live-language-skip-button-label"]`,
|
|
||||||
],
|
|
||||||
// Unexpected selectors:
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Ignore the telemetry of the initial welcome screen.
|
|
||||||
flushClickTelemetry();
|
|
||||||
|
|
||||||
resolveLangPacks(["es-MX", "es-ES", "fr-FR"]);
|
|
||||||
resolveInstaller();
|
|
||||||
|
|
||||||
await testScreenContent(
|
|
||||||
browser,
|
|
||||||
"Live language switching, asking for a language",
|
|
||||||
// Expected selectors:
|
|
||||||
[
|
|
||||||
...liveLanguageSwitchSelectors,
|
|
||||||
`[data-l10n-id="onboarding-live-language-switch-button-label"]`,
|
|
||||||
`[data-l10n-id="onboarding-live-language-not-now-button-label"]`,
|
|
||||||
],
|
|
||||||
// Unexpected selectors:
|
|
||||||
[
|
|
||||||
`button[disabled] [data-l10n-id="onboarding-live-language-waiting-button"]`,
|
|
||||||
`[data-l10n-id="onboarding-live-language-skip-button-label"]`,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
info("Clicking the secondary button to skip installing the langpack.");
|
|
||||||
await clickVisibleButton(browser, "button.secondary");
|
|
||||||
|
|
||||||
await testScreenContent(
|
|
||||||
browser,
|
|
||||||
"Language selection declined",
|
|
||||||
// Expected selectors:
|
|
||||||
[`.screen-2`],
|
|
||||||
// Unexpected selectors:
|
|
||||||
liveLanguageSwitchSelectors
|
|
||||||
);
|
|
||||||
|
|
||||||
eventsMatch(flushClickTelemetry(), [
|
|
||||||
{
|
|
||||||
event: "CLICK_BUTTON",
|
|
||||||
event_context: {
|
|
||||||
source: "decline",
|
|
||||||
page: "about:welcome",
|
|
||||||
},
|
|
||||||
message_id: "DEFAULT_ABOUTWELCOME_PROTON_1_AW_LANGUAGE_MISMATCH",
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure the langpack can be installed before the user gets to the language screen.
|
|
||||||
*/
|
|
||||||
add_task(async function test_aboutwelcome_languageSwitcher_asyncCalls() {
|
|
||||||
sandbox.restore();
|
|
||||||
const {
|
|
||||||
resolveLangPacks,
|
|
||||||
resolveInstaller,
|
|
||||||
mockable,
|
|
||||||
} = mockAddonAndLocaleAPIs({
|
|
||||||
systemLocale: "es-ES",
|
|
||||||
appLocale: "en-US",
|
|
||||||
});
|
|
||||||
|
|
||||||
await openAboutWelcome();
|
|
||||||
|
|
||||||
info("Waiting for getAvailableLangpacks to be called.");
|
|
||||||
await TestUtils.waitForCondition(
|
|
||||||
() => mockable.getAvailableLangpacks.called,
|
|
||||||
"getAvailableLangpacks called once"
|
|
||||||
);
|
|
||||||
ok(mockable.installLangPack.notCalled);
|
|
||||||
|
|
||||||
resolveLangPacks(["es-MX", "es-ES", "fr-FR"]);
|
|
||||||
|
|
||||||
await TestUtils.waitForCondition(
|
|
||||||
() => mockable.installLangPack.called,
|
|
||||||
"installLangPack was called once"
|
|
||||||
);
|
|
||||||
ok(mockable.getAvailableLangpacks.called);
|
|
||||||
|
|
||||||
resolveInstaller();
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test when AMO does not have a matching language.
|
|
||||||
*/
|
|
||||||
add_task(async function test_aboutwelcome_languageSwitcher_noMatch() {
|
|
||||||
sandbox.restore();
|
|
||||||
const { resolveLangPacks } = mockAddonAndLocaleAPIs({
|
|
||||||
systemLocale: "tlh", // Klingon
|
|
||||||
appLocale: "en-US",
|
|
||||||
});
|
|
||||||
|
|
||||||
const { browser } = await openAboutWelcome();
|
|
||||||
|
|
||||||
info("Clicking the primary button to start installing the langpack.");
|
|
||||||
await clickVisibleButton(browser, "button.primary");
|
|
||||||
|
|
||||||
// Klingon is not supported.
|
|
||||||
resolveLangPacks(["es-MX", "es-ES", "fr-FR"]);
|
|
||||||
|
|
||||||
await testScreenContent(
|
|
||||||
browser,
|
|
||||||
"Language selection skipped",
|
|
||||||
// Expected selectors:
|
|
||||||
[`.screen-1`],
|
|
||||||
// Unexpected selectors:
|
|
||||||
[
|
|
||||||
`[data-l10n-id*="onboarding-live-language"]`,
|
|
||||||
`[data-l10n-id="onboarding-live-language-header"]`,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test hitting the cancel button when waiting on a langpack.
|
|
||||||
*/
|
|
||||||
add_task(async function test_aboutwelcome_languageSwitcher_cancelWaiting() {
|
|
||||||
sandbox.restore();
|
|
||||||
const { resolveLangPacks, resolveInstaller } = mockAddonAndLocaleAPIs({
|
|
||||||
systemLocale: "es-ES",
|
|
||||||
appLocale: "en-US",
|
|
||||||
});
|
|
||||||
|
|
||||||
const { browser, flushClickTelemetry } = await openAboutWelcome();
|
|
||||||
|
|
||||||
info("Clicking the primary button to start the onboarding process.");
|
|
||||||
await clickVisibleButton(browser, "button.primary");
|
|
||||||
resolveLangPacks(["es-MX", "es-ES", "fr-FR"]);
|
|
||||||
|
|
||||||
await testScreenContent(
|
|
||||||
browser,
|
|
||||||
"Live language switching, asking for a language",
|
|
||||||
// Expected selectors:
|
|
||||||
liveLanguageSwitchSelectors,
|
|
||||||
// Unexpected selectors:
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
info("Clicking the primary button to view language switching page.");
|
|
||||||
await clickVisibleButton(browser, "button.primary");
|
|
||||||
|
|
||||||
await testScreenContent(
|
|
||||||
browser,
|
|
||||||
"Live language switching, waiting for langpack to download",
|
|
||||||
// Expected selectors:
|
|
||||||
[
|
|
||||||
...liveLanguageSwitchSelectors,
|
|
||||||
`[data-l10n-id="onboarding-live-language-button-label-downloading"]`,
|
|
||||||
`[data-l10n-id="onboarding-live-language-secondary-cancel-download"]`,
|
|
||||||
],
|
|
||||||
// Unexpected selectors:
|
|
||||||
[
|
|
||||||
`button[disabled] [data-l10n-id="onboarding-live-language-waiting-button"]`,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Ignore all the telemetry up to this point.
|
|
||||||
flushClickTelemetry();
|
|
||||||
|
|
||||||
info("Cancel the request for the language");
|
|
||||||
await clickVisibleButton(browser, "button.secondary");
|
|
||||||
|
|
||||||
await testScreenContent(
|
|
||||||
browser,
|
|
||||||
"Language selection declined waiting",
|
|
||||||
// Expected selectors:
|
|
||||||
[`.screen-2`],
|
|
||||||
// Unexpected selectors:
|
|
||||||
liveLanguageSwitchSelectors
|
|
||||||
);
|
|
||||||
|
|
||||||
eventsMatch(flushClickTelemetry(), [
|
|
||||||
{
|
|
||||||
event: "CLICK_BUTTON",
|
|
||||||
event_context: {
|
|
||||||
source: "cancel_waiting",
|
|
||||||
page: "about:welcome",
|
|
||||||
},
|
|
||||||
message_id: "DEFAULT_ABOUTWELCOME_PROTON_1_AW_LANGUAGE_MISMATCH",
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
await resolveInstaller();
|
|
||||||
|
|
||||||
is(flushClickTelemetry().length, 0);
|
|
||||||
});
|
|
|
@ -74,28 +74,28 @@ describe("MultiStageAboutWelcomeProton module", () => {
|
||||||
);
|
);
|
||||||
assert.propertyVal(data.screens[0], "id", "AW_PIN_FIREFOX");
|
assert.propertyVal(data.screens[0], "id", "AW_PIN_FIREFOX");
|
||||||
assert.propertyVal(data.screens[1], "id", "AW_SET_DEFAULT");
|
assert.propertyVal(data.screens[1], "id", "AW_SET_DEFAULT");
|
||||||
assert.lengthOf(data.screens, getData().screens.length - 1);
|
assert.lengthOf(data.screens, getData().screens.length);
|
||||||
});
|
});
|
||||||
it("should keep 'pin' and remove 'default' if already default", async () => {
|
it("should keep 'pin' and remove 'default' if already default", async () => {
|
||||||
const data = await prepConfig({ needPin: true });
|
const data = await prepConfig({ needPin: true });
|
||||||
|
|
||||||
assert.propertyVal(data.screens[0], "id", "AW_PIN_FIREFOX");
|
assert.propertyVal(data.screens[0], "id", "AW_PIN_FIREFOX");
|
||||||
assert.propertyVal(data.screens[1], "id", "AW_IMPORT_SETTINGS");
|
assert.propertyVal(data.screens[1], "id", "AW_IMPORT_SETTINGS");
|
||||||
assert.lengthOf(data.screens, getData().screens.length - 2);
|
assert.lengthOf(data.screens, getData().screens.length - 1);
|
||||||
});
|
});
|
||||||
it("should switch to 'default' if already pinned", async () => {
|
it("should switch to 'default' if already pinned", async () => {
|
||||||
const data = await prepConfig({ needDefault: true });
|
const data = await prepConfig({ needDefault: true });
|
||||||
|
|
||||||
assert.propertyVal(data.screens[0], "id", "AW_ONLY_DEFAULT");
|
assert.propertyVal(data.screens[0], "id", "AW_ONLY_DEFAULT");
|
||||||
assert.propertyVal(data.screens[1], "id", "AW_IMPORT_SETTINGS");
|
assert.propertyVal(data.screens[1], "id", "AW_IMPORT_SETTINGS");
|
||||||
assert.lengthOf(data.screens, getData().screens.length - 2);
|
assert.lengthOf(data.screens, getData().screens.length - 1);
|
||||||
});
|
});
|
||||||
it("should switch to 'start' if already pinned and default", async () => {
|
it("should switch to 'start' if already pinned and default", async () => {
|
||||||
const data = await prepConfig();
|
const data = await prepConfig();
|
||||||
|
|
||||||
assert.propertyVal(data.screens[0], "id", "AW_GET_STARTED");
|
assert.propertyVal(data.screens[0], "id", "AW_GET_STARTED");
|
||||||
assert.propertyVal(data.screens[1], "id", "AW_IMPORT_SETTINGS");
|
assert.propertyVal(data.screens[1], "id", "AW_IMPORT_SETTINGS");
|
||||||
assert.lengthOf(data.screens, getData().screens.length - 2);
|
assert.lengthOf(data.screens, getData().screens.length - 1);
|
||||||
});
|
});
|
||||||
it("should have a FxA button", async () => {
|
it("should have a FxA button", async () => {
|
||||||
const data = await prepConfig();
|
const data = await prepConfig();
|
||||||
|
|
|
@ -222,24 +222,3 @@ mr2-onboarding-default-theme-label = Explore default themes.
|
||||||
mr2-onboarding-thank-you-header = Thank you for choosing us
|
mr2-onboarding-thank-you-header = Thank you for choosing us
|
||||||
mr2-onboarding-thank-you-text = { -brand-short-name } is an independent browser backed by a non-profit. Together, we’re making the web safer, healthier, and more private.
|
mr2-onboarding-thank-you-text = { -brand-short-name } is an independent browser backed by a non-profit. Together, we’re making the web safer, healthier, and more private.
|
||||||
mr2-onboarding-start-browsing-button-label = Start browsing
|
mr2-onboarding-start-browsing-button-label = Start browsing
|
||||||
|
|
||||||
## Multistage live language reloading onboarding strings (about:welcome pages)
|
|
||||||
##
|
|
||||||
## The following language names are generated by the browser's Intl.DisplayNames API.
|
|
||||||
##
|
|
||||||
## Variables:
|
|
||||||
## $appLanguage (String) - The name of Firefox's language, e.g. "American English"
|
|
||||||
## $systemLanguage (String) - The name of the OS's language, e.g. "European Spanish"
|
|
||||||
## $negotiatedLanguage (String) - The name of the langpack's language, e.g. "European Spanish"
|
|
||||||
|
|
||||||
onboarding-live-language-header = Choose Your Language
|
|
||||||
onboarding-live-language-subtitle = { -brand-short-name } is using { $appLanguage } while your system is using { $systemLanguage }.
|
|
||||||
|
|
||||||
onboarding-live-language-switch-button-label = Switch to { $negotiatedLanguage }
|
|
||||||
onboarding-live-language-button-label-downloading = Downloading the language pack for { $negotiatedLanguage }…
|
|
||||||
onboarding-live-language-waiting-subtitle = It looks like your system and { -brand-short-name } are using different languages.
|
|
||||||
onboarding-live-language-waiting-button = Getting available languages…
|
|
||||||
onboarding-live-language-installing = Installing the language pack for { $negotiatedLanguage }…
|
|
||||||
onboarding-live-language-secondary-cancel-download = Cancel
|
|
||||||
onboarding-live-language-not-now-button-label = Not now
|
|
||||||
onboarding-live-language-skip-button-label = Skip
|
|
||||||
|
|
|
@ -1,311 +0,0 @@
|
||||||
/* 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 { XPCOMUtils } = ChromeUtils.import(
|
|
||||||
"resource://gre/modules/XPCOMUtils.jsm"
|
|
||||||
);
|
|
||||||
|
|
||||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
|
||||||
AddonRepository: "resource://gre/modules/addons/AddonRepository.jsm",
|
|
||||||
AddonManager: "resource://gre/modules/AddonManager.jsm",
|
|
||||||
Services: "resource://gre/modules/Services.jsm",
|
|
||||||
});
|
|
||||||
|
|
||||||
if (Services.appinfo.processType !== Services.appinfo.PROCESS_TYPE_DEFAULT) {
|
|
||||||
// This check ensures that the `mockable` API calls can be consisently mocked in tests.
|
|
||||||
// If this requirement needs to be eased, please ensure the test logic remains valid.
|
|
||||||
throw new Error("This code is assumed to run in the parent process.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to find an appropriate langpack for a given language. The async function
|
|
||||||
* is infallible, but may not return a langpack.
|
|
||||||
*
|
|
||||||
* @returns {LangPack | null}
|
|
||||||
*/
|
|
||||||
async function negotiateLangPackForLanguageMismatch() {
|
|
||||||
const localeInfo = getAppAndSystemLocaleInfo();
|
|
||||||
if (!localeInfo.systemLocale) {
|
|
||||||
// The system locale info was not valid.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch the available langpacks from AMO.
|
|
||||||
*
|
|
||||||
* @type {Array<LangPack>}
|
|
||||||
*/
|
|
||||||
const availableLangpacks = await mockable.getAvailableLangpacks();
|
|
||||||
if (!availableLangpacks) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Figure out a langpack to recommend.
|
|
||||||
* @type {LangPack | null}
|
|
||||||
*/
|
|
||||||
return (
|
|
||||||
// First look for a langpack that matches the baseName.
|
|
||||||
// e.g. system "fr-FR" matches langpack "fr-FR"
|
|
||||||
// system "en-GB" matches langpack "en-GB".
|
|
||||||
availableLangpacks.find(
|
|
||||||
({ target_locale }) => target_locale === localeInfo.systemLocale.baseName
|
|
||||||
) ||
|
|
||||||
// Next look for langpacks that just match the language.
|
|
||||||
// e.g. system "fr-FR" matches langpack "fr".
|
|
||||||
// system "en-AU" matches langpack "en".
|
|
||||||
availableLangpacks.find(
|
|
||||||
({ target_locale }) => target_locale === localeInfo.systemLocale.language
|
|
||||||
) ||
|
|
||||||
// Next look for a langpack that matches the language, but not the region.
|
|
||||||
// e.g. "es-CL" (Chilean Spanish) as a system language matching
|
|
||||||
// "es-ES" (European Spanish)
|
|
||||||
availableLangpacks.find(({ target_locale }) =>
|
|
||||||
target_locale.startsWith(`${localeInfo.systemLocale.language}-`)
|
|
||||||
) ||
|
|
||||||
null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a langpack is being installed, allow blocking on that.
|
|
||||||
let installingLangpack = new Map();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {LangPack}
|
|
||||||
* @type {object}
|
|
||||||
* @property {string} target_locale
|
|
||||||
* @property {string} url
|
|
||||||
* @property {string} hash
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure that a given lanpack is installed.
|
|
||||||
*
|
|
||||||
* @param {LangPack} langPack
|
|
||||||
* @returns {Promise<boolean>} Success or failure.
|
|
||||||
*/
|
|
||||||
function ensureLangPackInstalled(langPack) {
|
|
||||||
if (!langPack) {
|
|
||||||
throw new Error("Expected a LangPack to install.");
|
|
||||||
}
|
|
||||||
// Make sure any outstanding calls get resolved before attempting another call.
|
|
||||||
// This guards against any quick page refreshes attempting to install the langpack
|
|
||||||
// twice.
|
|
||||||
const inProgress = installingLangpack.get(langPack.hash);
|
|
||||||
if (inProgress) {
|
|
||||||
return inProgress;
|
|
||||||
}
|
|
||||||
const promise = _ensureLangPackInstalledImpl(langPack);
|
|
||||||
installingLangpack.set(langPack.hash, promise);
|
|
||||||
promise.finally(() => {
|
|
||||||
installingLangpack.delete(langPack.hash);
|
|
||||||
});
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {LangPack} langPack
|
|
||||||
* @returns {boolean} Success or failure.
|
|
||||||
*/
|
|
||||||
async function _ensureLangPackInstalledImpl(langPack) {
|
|
||||||
if (mockable.getAvailableLocales().includes(langPack.target_locale)) {
|
|
||||||
// The langpack is already installed.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return mockable.installLangPack(langPack);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* These are all functions with side effects or configuration options that should be
|
|
||||||
* mockable for tests.
|
|
||||||
*/
|
|
||||||
const mockable = {
|
|
||||||
/**
|
|
||||||
* @returns {LangPack[] | null}
|
|
||||||
*/
|
|
||||||
async getAvailableLangpacks() {
|
|
||||||
try {
|
|
||||||
return AddonRepository.getAvailableLangpacks();
|
|
||||||
} catch (error) {
|
|
||||||
Cu.reportError(
|
|
||||||
`Failed to get the list of available language packs: ${error?.message}`
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use the AddonManager to install an addon from the URL.
|
|
||||||
* @param {LangPack} langPack
|
|
||||||
*/
|
|
||||||
async installLangPack(langPack) {
|
|
||||||
let install;
|
|
||||||
try {
|
|
||||||
install = await AddonManager.getInstallForURL(langPack.url, {
|
|
||||||
hash: langPack.hash,
|
|
||||||
telemetryInfo: {
|
|
||||||
source: "about:welcome",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
Cu.reportError(error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await install.install();
|
|
||||||
} catch (error) {
|
|
||||||
Cu.reportError(error);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string[]}
|
|
||||||
*/
|
|
||||||
getAvailableLocales() {
|
|
||||||
return Services.locale.availableLocales;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
getAppLocaleAsBCP47() {
|
|
||||||
return Services.locale.appLocaleAsBCP47;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
getSystemLocale() {
|
|
||||||
// Allow the system locale to be overridden for manual testing.
|
|
||||||
const systemLocaleOverride = Services.prefs.getCharPref(
|
|
||||||
"intl.multilingual.aboutWelcome.systemLocaleOverride",
|
|
||||||
null
|
|
||||||
);
|
|
||||||
if (systemLocaleOverride) {
|
|
||||||
try {
|
|
||||||
// If the locale can't be parsed, ignore the pref.
|
|
||||||
new Services.intl.Locale(systemLocaleOverride);
|
|
||||||
return systemLocaleOverride;
|
|
||||||
} catch (_error) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const osPrefs = Cc["@mozilla.org/intl/ospreferences;1"].getService(
|
|
||||||
Ci.mozIOSPreferences
|
|
||||||
);
|
|
||||||
return osPrefs.systemLocale;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string[]} locales The BCP 47 locale identifiers.
|
|
||||||
*/
|
|
||||||
setRequestedAppLocales(locales) {
|
|
||||||
Services.locale.requestedLocales = locales;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function is really only setting `Services.locale.requestedLocales`, but it's
|
|
||||||
* using the `mockable` object to allow this behavior to be mocked in tests.
|
|
||||||
*
|
|
||||||
* @param {string[]} locales The BCP 47 locale identifiers.
|
|
||||||
*/
|
|
||||||
function setRequestedAppLocales(locales) {
|
|
||||||
mockable.setRequestedAppLocales(locales);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A serializable Intl.Locale.
|
|
||||||
*
|
|
||||||
* @typedef StructuredLocale
|
|
||||||
* @type {object}
|
|
||||||
* @property {string} baseName
|
|
||||||
* @property {string} language
|
|
||||||
* @property {string} region
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* In telemetry data, some of the system locales show up as blank. Guard against this
|
|
||||||
* and any other malformed locale information provided by the system by wrapping the call
|
|
||||||
* into a catch/try.
|
|
||||||
*
|
|
||||||
* @param {string} locale
|
|
||||||
* @returns {StructuredLocale | null}
|
|
||||||
*/
|
|
||||||
function getStructuredLocaleOrNull(localeString) {
|
|
||||||
try {
|
|
||||||
const locale = new Services.intl.Locale(localeString);
|
|
||||||
return {
|
|
||||||
baseName: locale.baseName,
|
|
||||||
language: locale.language,
|
|
||||||
region: locale.region,
|
|
||||||
};
|
|
||||||
} catch (_err) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine the system and app locales, and how much the locales match.
|
|
||||||
*
|
|
||||||
* @returns {{
|
|
||||||
* systemLocale: StructuredLocale,
|
|
||||||
* appLocale: StructuredLocale,
|
|
||||||
* matchType: "unknown" | "language-mismatch" | "region-mismatch" | "match",
|
|
||||||
* }}
|
|
||||||
*/
|
|
||||||
function getAppAndSystemLocaleInfo() {
|
|
||||||
// Convert locale strings into structured locale objects.
|
|
||||||
const systemLocaleRaw = mockable.getSystemLocale();
|
|
||||||
const appLocaleRaw = mockable.getAppLocaleAsBCP47();
|
|
||||||
|
|
||||||
const systemLocale = getStructuredLocaleOrNull(systemLocaleRaw);
|
|
||||||
const appLocale = getStructuredLocaleOrNull(appLocaleRaw);
|
|
||||||
|
|
||||||
let matchType = "unknown";
|
|
||||||
if (systemLocale && appLocale) {
|
|
||||||
if (systemLocale.language !== appLocale.language) {
|
|
||||||
matchType = "language-mismatch";
|
|
||||||
} else if (systemLocale.region !== appLocale.region) {
|
|
||||||
matchType = "region-mismatch";
|
|
||||||
} else {
|
|
||||||
matchType = "match";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const displayNames = new Services.intl.DisplayNames(appLocaleRaw, {
|
|
||||||
type: "language",
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
// Return the Intl.Locale in a serializable form.
|
|
||||||
systemLocaleRaw,
|
|
||||||
systemLocale,
|
|
||||||
appLocaleRaw,
|
|
||||||
appLocale,
|
|
||||||
matchType,
|
|
||||||
|
|
||||||
// These can be used as Fluent message args.
|
|
||||||
displayNames: {
|
|
||||||
systemLanguage: systemLocale
|
|
||||||
? displayNames.of(systemLocale.baseName)
|
|
||||||
: null,
|
|
||||||
appLanguage: appLocale ? displayNames.of(appLocale.baseName) : null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var LangPackMatcher = {
|
|
||||||
negotiateLangPackForLanguageMismatch,
|
|
||||||
ensureLangPackInstalled,
|
|
||||||
getAppAndSystemLocaleInfo,
|
|
||||||
setRequestedAppLocales,
|
|
||||||
mockable,
|
|
||||||
};
|
|
||||||
|
|
||||||
var EXPORTED_SYMBOLS = ["LangPackMatcher"];
|
|
|
@ -6,10 +6,6 @@
|
||||||
|
|
||||||
XPCSHELL_TESTS_MANIFESTS += ["tests/unit/xpcshell.ini"]
|
XPCSHELL_TESTS_MANIFESTS += ["tests/unit/xpcshell.ini"]
|
||||||
|
|
||||||
TESTING_JS_MODULES += [
|
|
||||||
"tests/LangPackMatcherTestUtils.jsm",
|
|
||||||
]
|
|
||||||
|
|
||||||
toolkit = CONFIG["MOZ_WIDGET_TOOLKIT"]
|
toolkit = CONFIG["MOZ_WIDGET_TOOLKIT"]
|
||||||
|
|
||||||
if toolkit == "windows":
|
if toolkit == "windows":
|
||||||
|
@ -51,7 +47,6 @@ UNIFIED_SOURCES += [
|
||||||
]
|
]
|
||||||
|
|
||||||
EXTRA_JS_MODULES += [
|
EXTRA_JS_MODULES += [
|
||||||
"LangPackMatcher.jsm",
|
|
||||||
"PluralForm.jsm",
|
"PluralForm.jsm",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -1,119 +0,0 @@
|
||||||
/* 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";
|
|
||||||
|
|
||||||
var EXPORTED_SYMBOLS = ["getAddonAndLocalAPIsMocker"];
|
|
||||||
|
|
||||||
const { LangPackMatcher } = ChromeUtils.import(
|
|
||||||
"resource://gre/modules/LangPackMatcher.jsm"
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* LangPackMatcher.jsm calls out to to the addons store, which involves network requests.
|
|
||||||
* Other tests create a fake addons server, and install mock XPIs. At the time of this
|
|
||||||
* writing that infrastructure is not available for mochitests.
|
|
||||||
*
|
|
||||||
* Instead, this test mocks out APIs that have a side-effect, so the addons of the browser
|
|
||||||
* are never modified.
|
|
||||||
*
|
|
||||||
* The calls to get the app's locale and system's locale are also mocked so that the
|
|
||||||
* different language mismatch scenarios can be run through.
|
|
||||||
*
|
|
||||||
* The locales are BCP 47 identifiers:
|
|
||||||
*
|
|
||||||
* @param {{
|
|
||||||
* sandbox: SinonSandbox,
|
|
||||||
* systemLocale: string,
|
|
||||||
* appLocale, string,
|
|
||||||
* }}
|
|
||||||
*/
|
|
||||||
function getAddonAndLocalAPIsMocker(testScope, sandbox) {
|
|
||||||
const { info } = testScope;
|
|
||||||
return function mockAddonAndLocaleAPIs({ systemLocale, appLocale }) {
|
|
||||||
info("Mocking LangPackMatcher.jsm APIs");
|
|
||||||
|
|
||||||
let resolveLangPacks;
|
|
||||||
const langPackPromise = new Promise(resolve => {
|
|
||||||
resolveLangPacks = availableLangpacks => {
|
|
||||||
info(
|
|
||||||
`Resolving which langpacks are available for download: ${JSON.stringify(
|
|
||||||
availableLangpacks
|
|
||||||
)}`
|
|
||||||
);
|
|
||||||
resolve(
|
|
||||||
availableLangpacks.map(locale => ({
|
|
||||||
guid: `langpack-${locale}@firefox.mozilla.org`,
|
|
||||||
type: "language",
|
|
||||||
target_locale: locale,
|
|
||||||
current_compatible_version: {
|
|
||||||
files: [
|
|
||||||
{
|
|
||||||
platform: "all",
|
|
||||||
url: `http://example.com/${locale}.langpack.xpi`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
let resolveInstaller;
|
|
||||||
const installerPromise = new Promise(resolve => {
|
|
||||||
resolveInstaller = () => {
|
|
||||||
info("LangPack install finished.");
|
|
||||||
resolve();
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const { mockable } = LangPackMatcher;
|
|
||||||
if (appLocale) {
|
|
||||||
sandbox.stub(mockable, "getAvailableLocales").returns([appLocale]);
|
|
||||||
sandbox.stub(mockable, "getAppLocaleAsBCP47").returns(appLocale);
|
|
||||||
}
|
|
||||||
if (systemLocale) {
|
|
||||||
sandbox.stub(mockable, "getSystemLocale").returns(systemLocale);
|
|
||||||
}
|
|
||||||
|
|
||||||
sandbox.stub(mockable, "getAvailableLangpacks").callsFake(() => {
|
|
||||||
info("Requesting which langpacks are available for download");
|
|
||||||
return langPackPromise;
|
|
||||||
});
|
|
||||||
|
|
||||||
sandbox.stub(mockable, "installLangPack").callsFake(langPack => {
|
|
||||||
info(`LangPack install started, but pending: ${langPack.target_locale}`);
|
|
||||||
return installerPromise;
|
|
||||||
});
|
|
||||||
|
|
||||||
sandbox.stub(mockable, "setRequestedAppLocales").callsFake(locales => {
|
|
||||||
info(
|
|
||||||
`Changing the browser's requested locales to: ${JSON.stringify(
|
|
||||||
locales
|
|
||||||
)}`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* Resolves the addons API call with available langpacks. Call with a list
|
|
||||||
* of BCP 47 identifiers.
|
|
||||||
*
|
|
||||||
* @type {(availableLangpacks: string[]) => {}}
|
|
||||||
*/
|
|
||||||
resolveLangPacks,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves the pending call to install a langpack.
|
|
||||||
*
|
|
||||||
* @type {() => {}}
|
|
||||||
*/
|
|
||||||
resolveInstaller,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The mocked APIs.
|
|
||||||
*/
|
|
||||||
mockable,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,201 +0,0 @@
|
||||||
/* 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 { getAddonAndLocalAPIsMocker } = ChromeUtils.import(
|
|
||||||
"resource://testing-common/LangPackMatcherTestUtils.jsm"
|
|
||||||
);
|
|
||||||
const { LangPackMatcher } = ChromeUtils.import(
|
|
||||||
"resource://gre/modules/LangPackMatcher.jsm"
|
|
||||||
);
|
|
||||||
const { sinon } = ChromeUtils.import("resource://testing-common/Sinon.jsm");
|
|
||||||
|
|
||||||
const sandbox = sinon.createSandbox();
|
|
||||||
const mockAddonAndLocaleAPIs = getAddonAndLocalAPIsMocker(this, sandbox);
|
|
||||||
|
|
||||||
add_task(function initSandbox() {
|
|
||||||
registerCleanupFunction(() => {
|
|
||||||
sandbox.restore();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
add_task(function test_appLocaleLanguageMismatch() {
|
|
||||||
sandbox.restore();
|
|
||||||
mockAddonAndLocaleAPIs({
|
|
||||||
systemLocale: "es-ES",
|
|
||||||
appLocale: "en-US",
|
|
||||||
});
|
|
||||||
|
|
||||||
deepEqual(LangPackMatcher.getAppAndSystemLocaleInfo(), {
|
|
||||||
systemLocaleRaw: "es-ES",
|
|
||||||
systemLocale: { baseName: "es-ES", language: "es", region: "ES" },
|
|
||||||
appLocaleRaw: "en-US",
|
|
||||||
appLocale: { baseName: "en-US", language: "en", region: "US" },
|
|
||||||
matchType: "language-mismatch",
|
|
||||||
displayNames: {
|
|
||||||
systemLanguage: "European Spanish",
|
|
||||||
appLanguage: "American English",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
add_task(function test_appLocaleRegionMismatch() {
|
|
||||||
sandbox.restore();
|
|
||||||
mockAddonAndLocaleAPIs({
|
|
||||||
sandbox,
|
|
||||||
systemLocale: "en-CA",
|
|
||||||
appLocale: "en-US",
|
|
||||||
});
|
|
||||||
|
|
||||||
deepEqual(LangPackMatcher.getAppAndSystemLocaleInfo(), {
|
|
||||||
systemLocaleRaw: "en-CA",
|
|
||||||
systemLocale: { baseName: "en-CA", language: "en", region: "CA" },
|
|
||||||
appLocaleRaw: "en-US",
|
|
||||||
appLocale: { baseName: "en-US", language: "en", region: "US" },
|
|
||||||
matchType: "region-mismatch",
|
|
||||||
displayNames: {
|
|
||||||
systemLanguage: "Canadian English",
|
|
||||||
appLanguage: "American English",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
add_task(function test_appLocaleScriptMismatch() {
|
|
||||||
sandbox.restore();
|
|
||||||
// Script mismatch:
|
|
||||||
mockAddonAndLocaleAPIs({
|
|
||||||
sandbox,
|
|
||||||
systemLocale: "zh-Hans-CN",
|
|
||||||
appLocale: "zh-CN",
|
|
||||||
});
|
|
||||||
|
|
||||||
deepEqual(LangPackMatcher.getAppAndSystemLocaleInfo(), {
|
|
||||||
systemLocaleRaw: "zh-Hans-CN",
|
|
||||||
systemLocale: { baseName: "zh-Hans-CN", language: "zh", region: "CN" },
|
|
||||||
appLocaleRaw: "zh-CN",
|
|
||||||
appLocale: { baseName: "zh-CN", language: "zh", region: "CN" },
|
|
||||||
matchType: "match",
|
|
||||||
displayNames: {
|
|
||||||
systemLanguage: "简体中文(中国)",
|
|
||||||
appLanguage: "中文(中国)",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
add_task(function test_appLocaleInvalidSystem() {
|
|
||||||
sandbox.restore();
|
|
||||||
// Script mismatch:
|
|
||||||
mockAddonAndLocaleAPIs({
|
|
||||||
sandbox,
|
|
||||||
systemLocale: "Not valid",
|
|
||||||
appLocale: "en-US",
|
|
||||||
});
|
|
||||||
|
|
||||||
deepEqual(LangPackMatcher.getAppAndSystemLocaleInfo(), {
|
|
||||||
systemLocaleRaw: "Not valid",
|
|
||||||
systemLocale: null,
|
|
||||||
appLocaleRaw: "en-US",
|
|
||||||
appLocale: { baseName: "en-US", language: "en", region: "US" },
|
|
||||||
matchType: "unknown",
|
|
||||||
displayNames: { systemLanguage: null, appLanguage: "American English" },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function shuffle(array) {
|
|
||||||
return array
|
|
||||||
.map(value => ({ value, sort: Math.random() }))
|
|
||||||
.sort((a, b) => a.sort - b.sort)
|
|
||||||
.map(({ value }) => value);
|
|
||||||
}
|
|
||||||
|
|
||||||
add_task(async function test_negotiateLangPacks() {
|
|
||||||
const negotiations = [
|
|
||||||
{
|
|
||||||
// Exact match found.
|
|
||||||
systemLocale: "en-US",
|
|
||||||
availableLangPacks: ["en", "en-US", "zh", "zh-CN", "zh-Hans-CN"],
|
|
||||||
expected: "en-US",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Region-less match.
|
|
||||||
systemLocale: "en-CA",
|
|
||||||
availableLangPacks: ["en", "en-US", "zh", "zh-CN", "zh-Hans-CN"],
|
|
||||||
expected: "en",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Fallback to a different region.
|
|
||||||
systemLocale: "en-CA",
|
|
||||||
availableLangPacks: ["en-US", "zh", "zh-CN", "zh-Hans-CN"],
|
|
||||||
expected: "en-US",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Match with a script. zh-Hans-CN is the locale used with simplified
|
|
||||||
// Chinese scripts, while zh-CN uses the Latin script.
|
|
||||||
systemLocale: "zh-Hans-CN",
|
|
||||||
availableLangPacks: ["en", "en-US", "zh", "zh-CN", "zh-Hans-CN"],
|
|
||||||
expected: "zh-Hans-CN",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// No reasonable match could be found.
|
|
||||||
systemLocale: "tlh", // Klingon
|
|
||||||
availableLangPacks: ["en", "en-US", "zh", "zh-CN", "zh-Hans-CN"],
|
|
||||||
expected: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Weird, but valid locale identifiers.
|
|
||||||
systemLocale: "en-US-u-hc-h23-ca-islamic-civil-ss-true",
|
|
||||||
availableLangPacks: ["en", "en-US", "zh", "zh-CN", "zh-Hans-CN"],
|
|
||||||
expected: "en-US",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Invalid system locale
|
|
||||||
systemLocale: "Not valid",
|
|
||||||
availableLangPacks: ["en", "en-US", "zh", "zh-CN", "zh-Hans-CN"],
|
|
||||||
expected: null,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const { systemLocale, availableLangPacks, expected } of negotiations) {
|
|
||||||
sandbox.restore();
|
|
||||||
const { resolveLangPacks } = mockAddonAndLocaleAPIs({
|
|
||||||
sandbox,
|
|
||||||
systemLocale,
|
|
||||||
});
|
|
||||||
|
|
||||||
const promise = LangPackMatcher.negotiateLangPackForLanguageMismatch();
|
|
||||||
// Shuffle the order to ensure that this test doesn't require on ordering of the
|
|
||||||
// langpack responses.
|
|
||||||
resolveLangPacks(shuffle(availableLangPacks));
|
|
||||||
const actual = (await promise)?.target_locale;
|
|
||||||
equal(
|
|
||||||
actual,
|
|
||||||
expected,
|
|
||||||
`Resolve the systemLocale "${systemLocale}" with available langpacks: ${JSON.stringify(
|
|
||||||
availableLangPacks
|
|
||||||
)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
add_task(async function test_ensureLangPackInstalled() {
|
|
||||||
sandbox.restore();
|
|
||||||
const { resolveLangPacks, resolveInstaller } = mockAddonAndLocaleAPIs({
|
|
||||||
sandbox,
|
|
||||||
systemLocale: "es-ES",
|
|
||||||
appLocale: "en-US",
|
|
||||||
});
|
|
||||||
|
|
||||||
const negotiatePromise = LangPackMatcher.negotiateLangPackForLanguageMismatch();
|
|
||||||
resolveLangPacks(["es-ES"]);
|
|
||||||
const langPack = await negotiatePromise;
|
|
||||||
|
|
||||||
const installPromise1 = LangPackMatcher.ensureLangPackInstalled(langPack);
|
|
||||||
const installPromise2 = LangPackMatcher.ensureLangPackInstalled(langPack);
|
|
||||||
|
|
||||||
resolveInstaller(["fake langpack"]);
|
|
||||||
|
|
||||||
info("Ensure both installers resolve when called twice in a row.");
|
|
||||||
await installPromise1;
|
|
||||||
await installPromise2;
|
|
||||||
ok(true, "Both were called.");
|
|
||||||
});
|
|
|
@ -10,7 +10,7 @@ skip-if = toolkit != "windows" && toolkit != "cocoa"
|
||||||
[test_bug1086527.js]
|
[test_bug1086527.js]
|
||||||
[test_intl_on_workers.js]
|
[test_intl_on_workers.js]
|
||||||
skip-if = toolkit == "android" # bug 1309447
|
skip-if = toolkit == "android" # bug 1309447
|
||||||
[test_langPackMatcher.js]
|
|
||||||
[test_pluralForm.js]
|
[test_pluralForm.js]
|
||||||
[test_pluralForm_english.js]
|
[test_pluralForm_english.js]
|
||||||
[test_pluralForm_makeGetter.js]
|
[test_pluralForm_makeGetter.js]
|
||||||
|
|
|
@ -125,12 +125,6 @@ aboutwelcome:
|
||||||
description: >-
|
description: >-
|
||||||
Should the urlbar should be focused when users first land on
|
Should the urlbar should be focused when users first land on
|
||||||
about:welcome?
|
about:welcome?
|
||||||
languageMismatchEnabled:
|
|
||||||
type: boolean
|
|
||||||
fallbackPref: intl.multilingual.aboutWelcome.languageMismatchEnabled
|
|
||||||
description: >-
|
|
||||||
Suggest to change the language on about:welcome when there is a mismatch with
|
|
||||||
the OS.
|
|
||||||
transitions:
|
transitions:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: Enable transition effect between screens
|
description: Enable transition effect between screens
|
||||||
|
|
Загрузка…
Ссылка в новой задаче