diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index af9c34967156..2b611eb48e0c 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -2174,8 +2174,6 @@ pref("app.normandy.onsync_skew_sec", 600); // live reloading when switching between LTR and RTL languages. pref("intl.multilingual.liveReload", 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 diff --git a/browser/components/newtab/aboutwelcome/AboutWelcomeChild.jsm b/browser/components/newtab/aboutwelcome/AboutWelcomeChild.jsm index 2a71a282cdc2..f6e22ca6162b 100644 --- a/browser/components/newtab/aboutwelcome/AboutWelcomeChild.jsm +++ b/browser/components/newtab/aboutwelcome/AboutWelcomeChild.jsm @@ -162,22 +162,6 @@ class AboutWelcomeChild extends JSWindowActorChild { Cu.exportFunction(this.AWFinish.bind(this), window, { 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) { return this.wrapPromise( this.sendQuery("AWPage:SELECT_THEME", data.toUpperCase()) @@ -236,11 +206,6 @@ class AboutWelcomeChild extends JSWindowActorChild { let featureConfig = NimbusFeatures.aboutwelcome.getAllVariables(); featureConfig.needDefault = await this.sendQuery("AWPage:NEED_DEFAULT"); 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(); // FeatureConfig (from prefs or experiments) has higher precendence // 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"; } - 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 * @override diff --git a/browser/components/newtab/aboutwelcome/AboutWelcomeParent.jsm b/browser/components/newtab/aboutwelcome/AboutWelcomeParent.jsm index 1f4fb31053ed..8a5ddfbf7c28 100644 --- a/browser/components/newtab/aboutwelcome/AboutWelcomeParent.jsm +++ b/browser/components/newtab/aboutwelcome/AboutWelcomeParent.jsm @@ -25,7 +25,6 @@ XPCOMUtils.defineLazyModuleGetters(this, { PromiseUtils: "resource://gre/modules/PromiseUtils.jsm", Region: "resource://gre/modules/Region.jsm", ShellService: "resource:///modules/ShellService.jsm", - LangPackMatcher: "resource://gre/modules/LangPackMatcher.jsm", }); 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: log.debug(`Unexpected event ${type} was not handled.`); } diff --git a/browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js b/browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js index 5f58c18863cc..621c2bf92bbb 100644 --- a/browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js +++ b/browser/components/newtab/aboutwelcome/content/aboutwelcome.bundle.js @@ -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___default = /*#__PURE__*/__webpack_require__.n(react_dom__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var _components_MultiStageAboutWelcome__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(3); -/* harmony import */ var _components_ReturnToAMO__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(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); } /* 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, utm_term: props.UTMTerm, transitions: props.transitions, - backdrop: props.backdrop, - appAndSystemLocaleInfo: props.appAndSystemLocaleInfo + backdrop: props.backdrop }); } @@ -277,8 +276,7 @@ __webpack_require__.r(__webpack_exports__); /* 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 _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_5__ = __webpack_require__(10); +/* harmony import */ var _asrouter_templates_FirstRun_addUtmParams__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(9); /* 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/. */ @@ -286,18 +284,14 @@ __webpack_require__.r(__webpack_exports__); - // Amount of milliseconds for all transitions to complete (including delays). const TRANSITION_OUT_TIME = 1000; const MultiStageAboutWelcome = props => { - let { - screens - } = props; const [index, setScreenIndex] = Object(react__WEBPACK_IMPORTED_MODULE_0__["useState"])(0); Object(react__WEBPACK_IMPORTED_MODULE_0__["useEffect"])(() => { // Send impression ping when respective screen first renders - screens.forEach((screen, order) => { + props.screens.forEach((screen, order) => { if (index === order) { _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 const handler = ({ 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 @@ -351,7 +345,7 @@ const MultiStageAboutWelcome = props => { setTransition(props.transitions ? "out" : ""); // Actually move forwards after all transitions finish. setTimeout(() => { - if (index < screens.length - 1) { + if (index < props.screens.length - 1) { setTransition(props.transitions ? "in" : ""); setScreenIndex(prevState => prevState + 1); } else { @@ -406,24 +400,18 @@ const MultiStageAboutWelcome = props => { })(); }, [useImportable, region]); 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", { className: `outer-wrapper onboardingContainer proton transition-${transition}`, style: 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 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, { key: screen.id + order, id: screen.id, - totalNumberOfScreens: screens.length, + totalNumberOfScreens: props.screens.length, isFirstCenteredScreen: isFirstCenteredScreen, isLastCenteredScreen: isLastCenteredScreen, order: order, @@ -436,9 +424,7 @@ const MultiStageAboutWelcome = props => { activeTheme: activeTheme, initialTheme: initialTheme, setActiveTheme: setActiveTheme, - autoAdvance: screen.auto_advance, - negotiatedLanguage: negotiatedLanguage, - langPackInstallPhase: langPackInstallPhase + autoAdvance: screen.auto_advance }) : null; }))); }; @@ -482,7 +468,7 @@ class WelcomeScreen extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureCom } = action; 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` }; @@ -497,7 +483,7 @@ class WelcomeScreen extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureCom }; } else if (type === "OPEN_URL") { 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) { url.searchParams.append("device_id", flowParams.deviceId); @@ -523,7 +509,7 @@ class WelcomeScreen extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureCom let { value } = 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)) { return; @@ -565,11 +551,7 @@ class WelcomeScreen extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureCom order: this.props.order, activeTheme: this.props.activeTheme, totalNumberOfScreens: this.props.totalNumberOfScreens - 1, - appAndSystemLocaleInfo: this.props.appAndSystemLocaleInfo, - negotiatedLanguage: this.props.negotiatedLanguage, - langPackInstallPhase: this.props.langPackInstallPhase, handleAction: this.handleAction, - messageId: this.props.messageId, isFirstCenteredScreen: this.props.isFirstCenteredScreen, isLastCenteredScreen: this.props.isLastCenteredScreen, 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 _Themes__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(8); /* 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 * 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/. */ @@ -799,7 +780,6 @@ __webpack_require__.r(__webpack_exports__); - const MultiStageProtonScreen = props => { const { autoAdvance, @@ -832,10 +812,7 @@ const MultiStageProtonScreen = props => { autoAdvance: props.autoAdvance, isRtamo: props.isRtamo, isTheme: props.isTheme, - iconURL: props.iconURL, - messageId: props.messageId, - negotiatedLanguage: props.negotiatedLanguage, - langPackInstallPhase: props.langPackInstallPhase + iconURL: props.iconURL }); }; 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); } - 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() { - var _this$props$appAndSys, _content$primary_butt; - const { autoAdvance, content, @@ -975,15 +940,13 @@ class ProtonScreen extends react__WEBPACK_IMPORTED_MODULE_0___default.a.PureComp text: content.subtitle }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("h2", { "data-l10n-args": JSON.stringify({ - "addon-name": this.props.addonName, - ...((_this$props$appAndSys = this.props.appAndSystemLocaleInfo) === null || _this$props$appAndSys === void 0 ? void 0 : _this$props$appAndSys.displayNames) + "addon-name": this.props.addonName }) - })) : 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 }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("button", { className: "primary", 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 })), content.secondary_button ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_MultiStageAboutWelcome__WEBPACK_IMPORTED_MODULE_4__["SecondaryCTA"], { content: content, @@ -1251,259 +1214,6 @@ const Themes = props => { /* 9 */ /***/ (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"; __webpack_require__.r(__webpack_exports__); /* 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__) { "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 _lib_aboutwelcome_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(5); /* 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 * 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/. */ diff --git a/browser/components/newtab/aboutwelcome/content/aboutwelcome.css b/browser/components/newtab/aboutwelcome/content/aboutwelcome.css index 643cce7c7e96..32081d30a154 100644 --- a/browser/components/newtab/aboutwelcome/content/aboutwelcome.css +++ b/browser/components/newtab/aboutwelcome/content/aboutwelcome.css @@ -211,20 +211,6 @@ body[lwt-newtab-brighttext] { .onboardingContainer .welcomeZap .zap.long::after { 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 { display: flex; flex-direction: column; diff --git a/browser/components/newtab/aboutwelcome/lib/AboutWelcomeDefaults.jsm b/browser/components/newtab/aboutwelcome/lib/AboutWelcomeDefaults.jsm index 3c0f0b78160c..bd0ac6cbb270 100644 --- a/browser/components/newtab/aboutwelcome/lib/AboutWelcomeDefaults.jsm +++ b/browser/components/newtab/aboutwelcome/lib/AboutWelcomeDefaults.jsm @@ -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", - order: 3, + order: 2, content: { title: { string_id: "mr1-onboarding-import-header", @@ -163,7 +135,7 @@ const DEFAULT_WELCOME_CONTENT = { }, { id: "AW_CHOOSE_THEME", - order: 4, + order: 3, content: { title: { string_id: "mr1-onboarding-theme-header", @@ -439,25 +411,6 @@ async function prepareContentForReact(content) { )?.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; } diff --git a/browser/components/newtab/content-src/aboutwelcome/aboutwelcome.jsx b/browser/components/newtab/content-src/aboutwelcome/aboutwelcome.jsx index c6875a5499a4..4084548a1c94 100644 --- a/browser/components/newtab/content-src/aboutwelcome/aboutwelcome.jsx +++ b/browser/components/newtab/content-src/aboutwelcome/aboutwelcome.jsx @@ -79,7 +79,6 @@ class AboutWelcome extends React.PureComponent { utm_term={props.UTMTerm} transitions={props.transitions} backdrop={props.backdrop} - appAndSystemLocaleInfo={props.appAndSystemLocaleInfo} /> ); } diff --git a/browser/components/newtab/content-src/aboutwelcome/aboutwelcome.scss b/browser/components/newtab/content-src/aboutwelcome/aboutwelcome.scss index b3b235c192e4..4425b19df152 100644 --- a/browser/components/newtab/content-src/aboutwelcome/aboutwelcome.scss +++ b/browser/components/newtab/content-src/aboutwelcome/aboutwelcome.scss @@ -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 { display: flex; flex-direction: column; diff --git a/browser/components/newtab/content-src/aboutwelcome/components/LanguageSwitcher.jsx b/browser/components/newtab/content-src/aboutwelcome/components/LanguageSwitcher.jsx deleted file mode 100644 index 1dc7f868d906..000000000000 --- a/browser/components/newtab/content-src/aboutwelcome/components/LanguageSwitcher.jsx +++ /dev/null @@ -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 ( - <> -
- -
- -
-
-
- -
- -
-
-
-
- -
-
- -
-
- - ); -} diff --git a/browser/components/newtab/content-src/aboutwelcome/components/MultiStageAboutWelcome.jsx b/browser/components/newtab/content-src/aboutwelcome/components/MultiStageAboutWelcome.jsx index 825fc50726e8..8afba1a15cef 100644 --- a/browser/components/newtab/content-src/aboutwelcome/components/MultiStageAboutWelcome.jsx +++ b/browser/components/newtab/content-src/aboutwelcome/components/MultiStageAboutWelcome.jsx @@ -6,7 +6,6 @@ import React, { useState, useEffect, useRef } from "react"; import { Localized } from "./MSLocalized"; import { AboutWelcomeUtils } from "../../lib/aboutwelcome-utils"; import { MultiStageProtonScreen } from "./MultiStageProtonScreen"; -import { useLanguageSwitcher } from "./LanguageSwitcher"; import { BASE_PARAMS, addUtmParams, @@ -16,12 +15,10 @@ import { const TRANSITION_OUT_TIME = 1000; export const MultiStageAboutWelcome = props => { - let { screens } = props; - const [index, setScreenIndex] = useState(0); useEffect(() => { // Send impression ping when respective screen first renders - screens.forEach((screen, order) => { + props.screens.forEach((screen, order) => { if (index === order) { AboutWelcomeUtils.sendImpressionTelemetry( `${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 // button from about:home 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 handler(window.history); @@ -84,7 +81,7 @@ export const MultiStageAboutWelcome = props => { // Actually move forwards after all transitions finish. setTimeout( () => { - if (index < screens.length - 1) { + if (index < props.screens.length - 1) { setTransition(props.transitions ? "in" : ""); setScreenIndex(prevState => prevState + 1); } else { @@ -143,26 +140,13 @@ export const MultiStageAboutWelcome = props => { s => s.content.position !== "corner" ); - const { - negotiatedLanguage, - langPackInstallPhase, - languageFilteredScreens, - } = useLanguageSwitcher( - props.appAndSystemLocaleInfo, - screens, - index, - setScreenIndex - ); - - screens = languageFilteredScreens; - return (
- {screens.map((screen, order) => { + {props.screens.map((screen, order) => { const isFirstCenteredScreen = screen.content.position !== "corner" && screen.order === centeredScreens[0].order; @@ -173,7 +157,7 @@ export const MultiStageAboutWelcome = props => { { initialTheme={initialTheme} setActiveTheme={setActiveTheme} autoAdvance={screen.auto_advance} - negotiatedLanguage={negotiatedLanguage} - langPackInstallPhase={langPackInstallPhase} /> ) : null; })} @@ -266,11 +248,7 @@ export class WelcomeScreen extends React.PureComponent { async handleAction(event) { let { props } = this; let { value } = 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)) { return; } @@ -317,11 +295,7 @@ export class WelcomeScreen extends React.PureComponent { order={this.props.order} activeTheme={this.props.activeTheme} totalNumberOfScreens={this.props.totalNumberOfScreens - 1} - appAndSystemLocaleInfo={this.props.appAndSystemLocaleInfo} - negotiatedLanguage={this.props.negotiatedLanguage} - langPackInstallPhase={this.props.langPackInstallPhase} handleAction={this.handleAction} - messageId={this.props.messageId} isFirstCenteredScreen={this.props.isFirstCenteredScreen} isLastCenteredScreen={this.props.isLastCenteredScreen} autoAdvance={this.props.autoAdvance} diff --git a/browser/components/newtab/content-src/aboutwelcome/components/MultiStageProtonScreen.jsx b/browser/components/newtab/content-src/aboutwelcome/components/MultiStageProtonScreen.jsx index 972bb958b62c..0862b1dc6e28 100644 --- a/browser/components/newtab/content-src/aboutwelcome/components/MultiStageProtonScreen.jsx +++ b/browser/components/newtab/content-src/aboutwelcome/components/MultiStageProtonScreen.jsx @@ -7,7 +7,6 @@ import { Localized } from "./MSLocalized"; import { Colorways } from "./Colorways"; import { Themes } from "./Themes"; import { SecondaryCTA, StepsIndicator } from "./MultiStageAboutWelcome"; -import { LanguageSwitcher } from "./LanguageSwitcher"; export const MultiStageProtonScreen = props => { const { autoAdvance, handleAction, order } = props; @@ -39,9 +38,6 @@ export const MultiStageProtonScreen = props => { isRtamo={props.isRtamo} isTheme={props.isTheme} 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 ? ( - - ) : null; - } - render() { const { autoAdvance, @@ -217,14 +201,12 @@ export class ProtonScreen extends React.PureComponent {

) : null}

{this.renderContentTiles()} - {this.renderLanguageSwitcher()}
diff --git a/browser/components/newtab/karma.mc.config.js b/browser/components/newtab/karma.mc.config.js index 3d09c8ac7ac1..5a811c4cc7cb 100644 --- a/browser/components/newtab/karma.mc.config.js +++ b/browser/components/newtab/karma.mc.config.js @@ -188,13 +188,6 @@ module.exports = function(config) { functions: 96, 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": { statements: 62, lines: 60, diff --git a/browser/components/newtab/test/browser/browser.ini b/browser/components/newtab/test/browser/browser.ini index 9e587bd4a601..4a2a79e92102 100644 --- a/browser/components/newtab/test/browser/browser.ini +++ b/browser/components/newtab/test/browser/browser.ini @@ -16,14 +16,12 @@ prefs = browser.newtabpage.activity-stream.feeds.section.topstories=true browser.newtabpage.activity-stream.feeds.section.topstories.options={"provider_name":""} messaging-system.log=all - intl.multilingual.aboutWelcome.languageMismatchEnabled=false [browser_aboutwelcome_configurable_ui.js] [browser_aboutwelcome_focus.js] [browser_aboutwelcome_multistage_default.js] [browser_aboutwelcome_multistage_primary.js] [browser_aboutwelcome_multistage_experimentAPI.js] -[browser_aboutwelcome_multistage_languageSwitcher.js] [browser_aboutwelcome_rtamo.js] skip-if = (os == "linux") # Test setup only implemented for OSX and Windows [browser_aboutwelcome_attribution.js] diff --git a/browser/components/newtab/test/browser/browser_aboutwelcome_multistage_languageSwitcher.js b/browser/components/newtab/test/browser/browser_aboutwelcome_multistage_languageSwitcher.js deleted file mode 100644 index 87f958d5a932..000000000000 --- a/browser/components/newtab/test/browser/browser_aboutwelcome_multistage_languageSwitcher.js +++ /dev/null @@ -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); -}); diff --git a/browser/components/newtab/test/unit/aboutwelcome/MultiStageAWProton.test.jsx b/browser/components/newtab/test/unit/aboutwelcome/MultiStageAWProton.test.jsx index 4dc9c198702b..7a797e913a8c 100644 --- a/browser/components/newtab/test/unit/aboutwelcome/MultiStageAWProton.test.jsx +++ b/browser/components/newtab/test/unit/aboutwelcome/MultiStageAWProton.test.jsx @@ -74,28 +74,28 @@ describe("MultiStageAboutWelcomeProton module", () => { ); assert.propertyVal(data.screens[0], "id", "AW_PIN_FIREFOX"); 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 () => { const data = await prepConfig({ needPin: true }); assert.propertyVal(data.screens[0], "id", "AW_PIN_FIREFOX"); 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 () => { const data = await prepConfig({ needDefault: true }); assert.propertyVal(data.screens[0], "id", "AW_ONLY_DEFAULT"); 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 () => { const data = await prepConfig(); assert.propertyVal(data.screens[0], "id", "AW_GET_STARTED"); 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 () => { const data = await prepConfig(); diff --git a/browser/locales/en-US/browser/newtab/onboarding.ftl b/browser/locales/en-US/browser/newtab/onboarding.ftl index 265c4c2cacf7..015f19522367 100644 --- a/browser/locales/en-US/browser/newtab/onboarding.ftl +++ b/browser/locales/en-US/browser/newtab/onboarding.ftl @@ -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-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 - -## 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 diff --git a/intl/locale/LangPackMatcher.jsm b/intl/locale/LangPackMatcher.jsm deleted file mode 100644 index ea30b4f0589e..000000000000 --- a/intl/locale/LangPackMatcher.jsm +++ /dev/null @@ -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} - */ - 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} 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"]; diff --git a/intl/locale/moz.build b/intl/locale/moz.build index 232e40007659..0a58e2964b91 100644 --- a/intl/locale/moz.build +++ b/intl/locale/moz.build @@ -6,10 +6,6 @@ XPCSHELL_TESTS_MANIFESTS += ["tests/unit/xpcshell.ini"] -TESTING_JS_MODULES += [ - "tests/LangPackMatcherTestUtils.jsm", -] - toolkit = CONFIG["MOZ_WIDGET_TOOLKIT"] if toolkit == "windows": @@ -51,7 +47,6 @@ UNIFIED_SOURCES += [ ] EXTRA_JS_MODULES += [ - "LangPackMatcher.jsm", "PluralForm.jsm", ] diff --git a/intl/locale/tests/LangPackMatcherTestUtils.jsm b/intl/locale/tests/LangPackMatcherTestUtils.jsm deleted file mode 100644 index 7a90e6c21e8d..000000000000 --- a/intl/locale/tests/LangPackMatcherTestUtils.jsm +++ /dev/null @@ -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, - }; - }; -} diff --git a/intl/locale/tests/unit/test_langPackMatcher.js b/intl/locale/tests/unit/test_langPackMatcher.js deleted file mode 100644 index 7b05d19191b2..000000000000 --- a/intl/locale/tests/unit/test_langPackMatcher.js +++ /dev/null @@ -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."); -}); diff --git a/intl/locale/tests/unit/xpcshell.ini b/intl/locale/tests/unit/xpcshell.ini index 1b213949a151..75ab01343430 100644 --- a/intl/locale/tests/unit/xpcshell.ini +++ b/intl/locale/tests/unit/xpcshell.ini @@ -10,7 +10,7 @@ skip-if = toolkit != "windows" && toolkit != "cocoa" [test_bug1086527.js] [test_intl_on_workers.js] skip-if = toolkit == "android" # bug 1309447 -[test_langPackMatcher.js] + [test_pluralForm.js] [test_pluralForm_english.js] [test_pluralForm_makeGetter.js] diff --git a/toolkit/components/nimbus/FeatureManifest.yaml b/toolkit/components/nimbus/FeatureManifest.yaml index b59b72f420e2..6d042be90af3 100644 --- a/toolkit/components/nimbus/FeatureManifest.yaml +++ b/toolkit/components/nimbus/FeatureManifest.yaml @@ -125,12 +125,6 @@ aboutwelcome: description: >- Should the urlbar should be focused when users first land on 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: type: boolean description: Enable transition effect between screens