/*! THIS FILE IS AUTO-GENERATED: webpack.system-addon.config.js */ var NewtabRenderUtils; /******/ (() => { // webpackBootstrap /******/ "use strict"; /******/ // The require scope /******/ var __webpack_require__ = {}; /******/ /************************************************************************/ /******/ /* webpack/runtime/compat get default export */ /******/ (() => { /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = (module) => { /******/ var getter = module && module.__esModule ? /******/ () => (module['default']) : /******/ () => (module); /******/ __webpack_require__.d(getter, { a: getter }); /******/ return getter; /******/ }; /******/ })(); /******/ /******/ /* webpack/runtime/define property getters */ /******/ (() => { /******/ // define getter functions for harmony exports /******/ __webpack_require__.d = (exports, definition) => { /******/ for(var key in definition) { /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); /******/ } /******/ } /******/ }; /******/ })(); /******/ /******/ /* webpack/runtime/global */ /******/ (() => { /******/ __webpack_require__.g = (function() { /******/ if (typeof globalThis === 'object') return globalThis; /******/ try { /******/ return this || new Function('return this')(); /******/ } catch (e) { /******/ if (typeof window === 'object') return window; /******/ } /******/ })(); /******/ })(); /******/ /******/ /* webpack/runtime/hasOwnProperty shorthand */ /******/ (() => { /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) /******/ })(); /******/ /******/ /* webpack/runtime/make namespace object */ /******/ (() => { /******/ // define __esModule on exports /******/ __webpack_require__.r = (exports) => { /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); /******/ } /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; /******/ })(); /******/ /************************************************************************/ var __webpack_exports__ = {}; // ESM COMPAT FLAG __webpack_require__.r(__webpack_exports__); // EXPORTS __webpack_require__.d(__webpack_exports__, { "NewTab": () => (/* binding */ NewTab), "renderCache": () => (/* binding */ renderCache), "renderWithoutState": () => (/* binding */ renderWithoutState) }); ;// CONCATENATED MODULE: ./common/Actions.sys.mjs /* 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 MAIN_MESSAGE_TYPE = "ActivityStream:Main"; const CONTENT_MESSAGE_TYPE = "ActivityStream:Content"; const PRELOAD_MESSAGE_TYPE = "ActivityStream:PreloadedBrowser"; const UI_CODE = 1; const BACKGROUND_PROCESS = 2; /** * globalImportContext - Are we in UI code (i.e. react, a dom) or some kind of background process? * Use this in action creators if you need different logic * for ui/background processes. */ const globalImportContext = typeof Window === "undefined" ? BACKGROUND_PROCESS : UI_CODE; // Create an object that avoids accidental differing key/value pairs: // { // INIT: "INIT", // UNINIT: "UNINIT" // } const actionTypes = {}; for (const type of [ "ABOUT_SPONSORED_TOP_SITES", "ADDONS_INFO_REQUEST", "ADDONS_INFO_RESPONSE", "ARCHIVE_FROM_POCKET", "AS_ROUTER_INITIALIZED", "AS_ROUTER_PREF_CHANGED", "AS_ROUTER_TARGETING_UPDATE", "AS_ROUTER_TELEMETRY_USER_EVENT", "BLOCK_URL", "BOOKMARK_URL", "CLEAR_PREF", "COPY_DOWNLOAD_LINK", "DELETE_BOOKMARK_BY_ID", "DELETE_FROM_POCKET", "DELETE_HISTORY_URL", "DIALOG_CANCEL", "DIALOG_OPEN", "DISABLE_SEARCH", "DISCOVERY_STREAM_COLLECTION_DISMISSIBLE_TOGGLE", "DISCOVERY_STREAM_CONFIG_CHANGE", "DISCOVERY_STREAM_CONFIG_RESET", "DISCOVERY_STREAM_CONFIG_RESET_DEFAULTS", "DISCOVERY_STREAM_CONFIG_SETUP", "DISCOVERY_STREAM_CONFIG_SET_VALUE", "DISCOVERY_STREAM_DEV_EXPIRE_CACHE", "DISCOVERY_STREAM_DEV_IDLE_DAILY", "DISCOVERY_STREAM_DEV_SYNC_RS", "DISCOVERY_STREAM_DEV_SYSTEM_TICK", "DISCOVERY_STREAM_EXPERIMENT_DATA", "DISCOVERY_STREAM_FEEDS_UPDATE", "DISCOVERY_STREAM_FEED_UPDATE", "DISCOVERY_STREAM_IMPRESSION_STATS", "DISCOVERY_STREAM_LAYOUT_RESET", "DISCOVERY_STREAM_LAYOUT_UPDATE", "DISCOVERY_STREAM_LINK_BLOCKED", "DISCOVERY_STREAM_LOADED_CONTENT", "DISCOVERY_STREAM_PERSONALIZATION_INIT", "DISCOVERY_STREAM_PERSONALIZATION_LAST_UPDATED", "DISCOVERY_STREAM_PERSONALIZATION_TOGGLE", "DISCOVERY_STREAM_POCKET_STATE_INIT", "DISCOVERY_STREAM_POCKET_STATE_SET", "DISCOVERY_STREAM_PREFS_SETUP", "DISCOVERY_STREAM_RECENT_SAVES", "DISCOVERY_STREAM_RETRY_FEED", "DISCOVERY_STREAM_SPOCS_CAPS", "DISCOVERY_STREAM_SPOCS_ENDPOINT", "DISCOVERY_STREAM_SPOCS_PLACEMENTS", "DISCOVERY_STREAM_SPOCS_UPDATE", "DISCOVERY_STREAM_SPOC_BLOCKED", "DISCOVERY_STREAM_SPOC_IMPRESSION", "DISCOVERY_STREAM_USER_EVENT", "DOWNLOAD_CHANGED", "FAKE_FOCUS_SEARCH", "FILL_SEARCH_TERM", "HANDOFF_SEARCH_TO_AWESOMEBAR", "HIDE_PERSONALIZE", "HIDE_PRIVACY_INFO", "INIT", "NEW_TAB_INIT", "NEW_TAB_INITIAL_STATE", "NEW_TAB_LOAD", "NEW_TAB_REHYDRATED", "NEW_TAB_STATE_REQUEST", "NEW_TAB_UNLOAD", "OPEN_DOWNLOAD_FILE", "OPEN_LINK", "OPEN_NEW_WINDOW", "OPEN_PRIVATE_WINDOW", "OPEN_WEBEXT_SETTINGS", "PARTNER_LINK_ATTRIBUTION", "PLACES_BOOKMARKS_REMOVED", "PLACES_BOOKMARK_ADDED", "PLACES_HISTORY_CLEARED", "PLACES_LINKS_CHANGED", "PLACES_LINKS_DELETED", "PLACES_LINK_BLOCKED", "PLACES_SAVED_TO_POCKET", "POCKET_CTA", "POCKET_LINK_DELETED_OR_ARCHIVED", "POCKET_LOGGED_IN", "POCKET_WAITING_FOR_SPOC", "PREFS_INITIAL_VALUES", "PREF_CHANGED", "PREVIEW_REQUEST", "PREVIEW_REQUEST_CANCEL", "PREVIEW_RESPONSE", "REMOVE_DOWNLOAD_FILE", "RICH_ICON_MISSING", "SAVE_SESSION_PERF_DATA", "SAVE_TO_POCKET", "SCREENSHOT_UPDATED", "SECTION_DEREGISTER", "SECTION_DISABLE", "SECTION_ENABLE", "SECTION_MOVE", "SECTION_OPTIONS_CHANGED", "SECTION_REGISTER", "SECTION_UPDATE", "SECTION_UPDATE_CARD", "SETTINGS_CLOSE", "SETTINGS_OPEN", "SET_PREF", "SHOW_DOWNLOAD_FILE", "SHOW_FIREFOX_ACCOUNTS", "SHOW_PERSONALIZE", "SHOW_PRIVACY_INFO", "SHOW_SEARCH", "SKIPPED_SIGNIN", "SNIPPETS_BLOCKLIST_CLEARED", "SNIPPETS_BLOCKLIST_UPDATED", "SNIPPETS_DATA", "SNIPPETS_PREVIEW_MODE", "SNIPPETS_RESET", "SNIPPET_BLOCKED", "SUBMIT_EMAIL", "SUBMIT_SIGNIN", "SYSTEM_TICK", "TELEMETRY_IMPRESSION_STATS", "TELEMETRY_USER_EVENT", "TOP_SITES_CANCEL_EDIT", "TOP_SITES_CLOSE_SEARCH_SHORTCUTS_MODAL", "TOP_SITES_EDIT", "TOP_SITES_INSERT", "TOP_SITES_OPEN_SEARCH_SHORTCUTS_MODAL", "TOP_SITES_ORGANIC_IMPRESSION_STATS", "TOP_SITES_PIN", "TOP_SITES_PREFS_UPDATED", "TOP_SITES_SPONSORED_IMPRESSION_STATS", "TOP_SITES_UNPIN", "TOP_SITES_UPDATED", "TOTAL_BOOKMARKS_REQUEST", "TOTAL_BOOKMARKS_RESPONSE", "UNINIT", "UPDATE_PINNED_SEARCH_SHORTCUTS", "UPDATE_SEARCH_SHORTCUTS", "UPDATE_SECTION_PREFS", "WEBEXT_CLICK", "WEBEXT_DISMISS", ]) { actionTypes[type] = type; } // Helper function for creating routed actions between content and main // Not intended to be used by consumers function _RouteMessage(action, options) { const meta = action.meta ? { ...action.meta } : {}; if (!options || !options.from || !options.to) { throw new Error( "Routed Messages must have options as the second parameter, and must at least include a .from and .to property." ); } // For each of these fields, if they are passed as an option, // add them to the action. If they are not defined, remove them. ["from", "to", "toTarget", "fromTarget", "skipMain", "skipLocal"].forEach( o => { if (typeof options[o] !== "undefined") { meta[o] = options[o]; } else if (meta[o]) { delete meta[o]; } } ); return { ...action, meta }; } /** * AlsoToMain - Creates a message that will be dispatched locally and also sent to the Main process. * * @param {object} action Any redux action (required) * @param {object} options * @param {bool} skipLocal Used by OnlyToMain to skip the main reducer * @param {string} fromTarget The id of the content port from which the action originated. (optional) * @return {object} An action with added .meta properties */ function AlsoToMain(action, fromTarget, skipLocal) { return _RouteMessage(action, { from: CONTENT_MESSAGE_TYPE, to: MAIN_MESSAGE_TYPE, fromTarget, skipLocal, }); } /** * OnlyToMain - Creates a message that will be sent to the Main process and skip the local reducer. * * @param {object} action Any redux action (required) * @param {object} options * @param {string} fromTarget The id of the content port from which the action originated. (optional) * @return {object} An action with added .meta properties */ function OnlyToMain(action, fromTarget) { return AlsoToMain(action, fromTarget, true); } /** * BroadcastToContent - Creates a message that will be dispatched to main and sent to ALL content processes. * * @param {object} action Any redux action (required) * @return {object} An action with added .meta properties */ function BroadcastToContent(action) { return _RouteMessage(action, { from: MAIN_MESSAGE_TYPE, to: CONTENT_MESSAGE_TYPE, }); } /** * AlsoToOneContent - Creates a message that will be will be dispatched to the main store * and also sent to a particular Content process. * * @param {object} action Any redux action (required) * @param {string} target The id of a content port * @param {bool} skipMain Used by OnlyToOneContent to skip the main process * @return {object} An action with added .meta properties */ function AlsoToOneContent(action, target, skipMain) { if (!target) { throw new Error( "You must provide a target ID as the second parameter of AlsoToOneContent. If you want to send to all content processes, use BroadcastToContent" ); } return _RouteMessage(action, { from: MAIN_MESSAGE_TYPE, to: CONTENT_MESSAGE_TYPE, toTarget: target, skipMain, }); } /** * OnlyToOneContent - Creates a message that will be sent to a particular Content process * and skip the main reducer. * * @param {object} action Any redux action (required) * @param {string} target The id of a content port * @return {object} An action with added .meta properties */ function OnlyToOneContent(action, target) { return AlsoToOneContent(action, target, true); } /** * AlsoToPreloaded - Creates a message that dispatched to the main reducer and also sent to the preloaded tab. * * @param {object} action Any redux action (required) * @return {object} An action with added .meta properties */ function AlsoToPreloaded(action) { return _RouteMessage(action, { from: MAIN_MESSAGE_TYPE, to: PRELOAD_MESSAGE_TYPE, }); } /** * UserEvent - A telemetry ping indicating a user action. This should only * be sent from the UI during a user session. * * @param {object} data Fields to include in the ping (source, etc.) * @return {object} An AlsoToMain action */ function UserEvent(data) { return AlsoToMain({ type: actionTypes.TELEMETRY_USER_EVENT, data, }); } /** * DiscoveryStreamUserEvent - A telemetry ping indicating a user action from Discovery Stream. This should only * be sent from the UI during a user session. * * @param {object} data Fields to include in the ping (source, etc.) * @return {object} An AlsoToMain action */ function DiscoveryStreamUserEvent(data) { return AlsoToMain({ type: actionTypes.DISCOVERY_STREAM_USER_EVENT, data, }); } /** * ASRouterUserEvent - A telemetry ping indicating a user action from AS router. This should only * be sent from the UI during a user session. * * @param {object} data Fields to include in the ping (source, etc.) * @return {object} An AlsoToMain action */ function ASRouterUserEvent(data) { return AlsoToMain({ type: actionTypes.AS_ROUTER_TELEMETRY_USER_EVENT, data, }); } /** * ImpressionStats - A telemetry ping indicating an impression stats. * * @param {object} data Fields to include in the ping * @param {int} importContext (For testing) Override the import context for testing. * #return {object} An action. For UI code, a AlsoToMain action. */ function ImpressionStats(data, importContext = globalImportContext) { const action = { type: actionTypes.TELEMETRY_IMPRESSION_STATS, data, }; return importContext === UI_CODE ? AlsoToMain(action) : action; } /** * DiscoveryStreamImpressionStats - A telemetry ping indicating an impression stats in Discovery Stream. * * @param {object} data Fields to include in the ping * @param {int} importContext (For testing) Override the import context for testing. * #return {object} An action. For UI code, a AlsoToMain action. */ function DiscoveryStreamImpressionStats( data, importContext = globalImportContext ) { const action = { type: actionTypes.DISCOVERY_STREAM_IMPRESSION_STATS, data, }; return importContext === UI_CODE ? AlsoToMain(action) : action; } /** * DiscoveryStreamLoadedContent - A telemetry ping indicating a content gets loaded in Discovery Stream. * * @param {object} data Fields to include in the ping * @param {int} importContext (For testing) Override the import context for testing. * #return {object} An action. For UI code, a AlsoToMain action. */ function DiscoveryStreamLoadedContent( data, importContext = globalImportContext ) { const action = { type: actionTypes.DISCOVERY_STREAM_LOADED_CONTENT, data, }; return importContext === UI_CODE ? AlsoToMain(action) : action; } function SetPref(name, value, importContext = globalImportContext) { const action = { type: actionTypes.SET_PREF, data: { name, value } }; return importContext === UI_CODE ? AlsoToMain(action) : action; } function WebExtEvent(type, data, importContext = globalImportContext) { if (!data || !data.source) { throw new Error( 'WebExtEvent actions should include a property "source", the id of the webextension that should receive the event.' ); } const action = { type, data }; return importContext === UI_CODE ? AlsoToMain(action) : action; } const actionCreators = { BroadcastToContent, UserEvent, DiscoveryStreamUserEvent, ASRouterUserEvent, ImpressionStats, AlsoToOneContent, OnlyToOneContent, AlsoToMain, OnlyToMain, AlsoToPreloaded, SetPref, WebExtEvent, DiscoveryStreamImpressionStats, DiscoveryStreamLoadedContent, }; // These are helpers to test for certain kinds of actions const actionUtils = { isSendToMain(action) { if (!action.meta) { return false; } return ( action.meta.to === MAIN_MESSAGE_TYPE && action.meta.from === CONTENT_MESSAGE_TYPE ); }, isBroadcastToContent(action) { if (!action.meta) { return false; } if (action.meta.to === CONTENT_MESSAGE_TYPE && !action.meta.toTarget) { return true; } return false; }, isSendToOneContent(action) { if (!action.meta) { return false; } if (action.meta.to === CONTENT_MESSAGE_TYPE && action.meta.toTarget) { return true; } return false; }, isSendToPreloaded(action) { if (!action.meta) { return false; } return ( action.meta.to === PRELOAD_MESSAGE_TYPE && action.meta.from === MAIN_MESSAGE_TYPE ); }, isFromMain(action) { if (!action.meta) { return false; } return ( action.meta.from === MAIN_MESSAGE_TYPE && action.meta.to === CONTENT_MESSAGE_TYPE ); }, getPortIdOfSender(action) { return (action.meta && action.meta.fromTarget) || null; }, _RouteMessage, }; ;// CONCATENATED MODULE: ./common/ActorConstants.sys.mjs /* vim: set ts=2 sw=2 sts=2 et tw=80: */ /* 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 MESSAGE_TYPE_LIST = [ "BLOCK_MESSAGE_BY_ID", "USER_ACTION", "IMPRESSION", "TRIGGER", "NEWTAB_MESSAGE_REQUEST", // PB is Private Browsing "PBNEWTAB_MESSAGE_REQUEST", "DOORHANGER_TELEMETRY", "TOOLBAR_BADGE_TELEMETRY", "TOOLBAR_PANEL_TELEMETRY", "MOMENTS_PAGE_TELEMETRY", "INFOBAR_TELEMETRY", "SPOTLIGHT_TELEMETRY", "TOAST_NOTIFICATION_TELEMETRY", "AS_ROUTER_TELEMETRY_USER_EVENT", // Admin types "ADMIN_CONNECT_STATE", "UNBLOCK_MESSAGE_BY_ID", "UNBLOCK_ALL", "BLOCK_BUNDLE", "UNBLOCK_BUNDLE", "DISABLE_PROVIDER", "ENABLE_PROVIDER", "EVALUATE_JEXL_EXPRESSION", "EXPIRE_QUERY_CACHE", "FORCE_ATTRIBUTION", "FORCE_WHATSNEW_PANEL", "FORCE_PRIVATE_BROWSING_WINDOW", "CLOSE_WHATSNEW_PANEL", "OVERRIDE_MESSAGE", "MODIFY_MESSAGE_JSON", "RESET_PROVIDER_PREF", "SET_PROVIDER_USER_PREF", "RESET_GROUPS_STATE", ]; const MESSAGE_TYPE_HASH = MESSAGE_TYPE_LIST.reduce((hash, value) => { hash[value] = value; return hash; }, {}); ;// CONCATENATED MODULE: ./content-src/asrouter/asrouter-utils.js /* 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 ASRouterUtils = { addListener(listener) { if (__webpack_require__.g.ASRouterAddParentListener) { __webpack_require__.g.ASRouterAddParentListener(listener); } }, removeListener(listener) { if (__webpack_require__.g.ASRouterRemoveParentListener) { __webpack_require__.g.ASRouterRemoveParentListener(listener); } }, sendMessage(action) { if (__webpack_require__.g.ASRouterMessage) { return __webpack_require__.g.ASRouterMessage(action); } throw new Error(`Unexpected call:\n${JSON.stringify(action, null, 3)}`); }, blockById(id, options) { return ASRouterUtils.sendMessage({ type: MESSAGE_TYPE_HASH.BLOCK_MESSAGE_BY_ID, data: { id, ...options } }); }, modifyMessageJson(content) { return ASRouterUtils.sendMessage({ type: MESSAGE_TYPE_HASH.MODIFY_MESSAGE_JSON, data: { content } }); }, executeAction(button_action) { return ASRouterUtils.sendMessage({ type: MESSAGE_TYPE_HASH.USER_ACTION, data: button_action }); }, unblockById(id) { return ASRouterUtils.sendMessage({ type: MESSAGE_TYPE_HASH.UNBLOCK_MESSAGE_BY_ID, data: { id } }); }, blockBundle(bundle) { return ASRouterUtils.sendMessage({ type: MESSAGE_TYPE_HASH.BLOCK_BUNDLE, data: { bundle } }); }, unblockBundle(bundle) { return ASRouterUtils.sendMessage({ type: MESSAGE_TYPE_HASH.UNBLOCK_BUNDLE, data: { bundle } }); }, overrideMessage(id) { return ASRouterUtils.sendMessage({ type: MESSAGE_TYPE_HASH.OVERRIDE_MESSAGE, data: { id } }); }, sendTelemetry(ping) { return ASRouterUtils.sendMessage(actionCreators.ASRouterUserEvent(ping)); }, getPreviewEndpoint() { if (__webpack_require__.g.document && __webpack_require__.g.document.location && __webpack_require__.g.document.location.href.includes("endpoint")) { const params = new URLSearchParams(__webpack_require__.g.document.location.href.slice(__webpack_require__.g.document.location.href.indexOf("endpoint"))); try { const endpoint = new URL(params.get("endpoint")); return { url: endpoint.href, snippetId: params.get("snippetId"), theme: this.getPreviewTheme(), dir: this.getPreviewDir() }; } catch (e) {} } return null; }, getPreviewTheme() { return new URLSearchParams(__webpack_require__.g.document.location.href.slice(__webpack_require__.g.document.location.href.indexOf("theme"))).get("theme"); }, getPreviewDir() { return new URLSearchParams(__webpack_require__.g.document.location.href.slice(__webpack_require__.g.document.location.href.indexOf("dir"))).get("dir"); } }; ;// CONCATENATED MODULE: external "ReactRedux" const external_ReactRedux_namespaceObject = ReactRedux; ;// CONCATENATED MODULE: external "React" const external_React_namespaceObject = React; var external_React_default = /*#__PURE__*/__webpack_require__.n(external_React_namespaceObject); ;// CONCATENATED MODULE: ./content-src/components/ASRouterAdmin/SimpleHashRouter.jsx /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ class SimpleHashRouter extends (external_React_default()).PureComponent { constructor(props) { super(props); this.onHashChange = this.onHashChange.bind(this); this.state = { hash: __webpack_require__.g.location.hash }; } onHashChange() { this.setState({ hash: __webpack_require__.g.location.hash }); } componentWillMount() { __webpack_require__.g.addEventListener("hashchange", this.onHashChange); } componentWillUnmount() { __webpack_require__.g.removeEventListener("hashchange", this.onHashChange); } render() { const [, ...routes] = this.state.hash.split("-"); return /*#__PURE__*/external_React_default().cloneElement(this.props.children, { location: { hash: this.state.hash, routes } }); } } ;// CONCATENATED MODULE: ./content-src/components/ASRouterAdmin/CopyButton.jsx function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ const CopyButton = ({ className, label, copiedLabel, inputSelector, transformer, ...props }) => { const [copied, setCopied] = (0,external_React_namespaceObject.useState)(false); const timeout = (0,external_React_namespaceObject.useRef)(null); const onClick = (0,external_React_namespaceObject.useCallback)(() => { let text = document.querySelector(inputSelector).value; if (transformer) text = transformer(text); navigator.clipboard.writeText(text); clearTimeout(timeout.current); setCopied(true); timeout.current = setTimeout(() => setCopied(false), 1500); }, [inputSelector, transformer]); return /*#__PURE__*/external_React_default().createElement("button", _extends({ className: className, onClick: e => onClick() }, props), copied && copiedLabel || label); }; ;// CONCATENATED MODULE: ./content-src/components/ASRouterAdmin/ASRouterAdmin.jsx function ASRouterAdmin_extends() { ASRouterAdmin_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 ASRouterAdmin_extends.apply(this, arguments); } /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ const Row = props => /*#__PURE__*/external_React_default().createElement("tr", ASRouterAdmin_extends({ className: "message-item" }, props), props.children); function relativeTime(timestamp) { if (!timestamp) { return ""; } const seconds = Math.floor((Date.now() - timestamp) / 1000); const minutes = Math.floor((Date.now() - timestamp) / 60000); if (seconds < 2) { return "just now"; } else if (seconds < 60) { return `${seconds} seconds ago`; } else if (minutes === 1) { return "1 minute ago"; } else if (minutes < 600) { return `${minutes} minutes ago`; } return new Date(timestamp).toLocaleString(); } const LAYOUT_VARIANTS = { basic: "Basic default layout (on by default in nightly)", staging_spocs: "A layout with all spocs shown", "dev-test-all": "A little bit of everything. Good layout for testing all components", "dev-test-feeds": "Stress testing for slow feeds" }; class ToggleStoryButton extends (external_React_default()).PureComponent { constructor(props) { super(props); this.handleClick = this.handleClick.bind(this); } handleClick() { this.props.onClick(this.props.story); } render() { return /*#__PURE__*/external_React_default().createElement("button", { onClick: this.handleClick }, "collapse/open"); } } class ToggleMessageJSON extends (external_React_default()).PureComponent { constructor(props) { super(props); this.handleClick = this.handleClick.bind(this); } handleClick() { this.props.toggleJSON(this.props.msgId); } render() { let iconName = this.props.isCollapsed ? "icon icon-arrowhead-forward-small" : "icon icon-arrowhead-down-small"; return /*#__PURE__*/external_React_default().createElement("button", { className: "clearButton", onClick: this.handleClick }, /*#__PURE__*/external_React_default().createElement("span", { className: iconName })); } } class TogglePrefCheckbox extends (external_React_default()).PureComponent { constructor(props) { super(props); this.onChange = this.onChange.bind(this); } onChange(event) { this.props.onChange(this.props.pref, event.target.checked); } render() { return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement("input", { type: "checkbox", checked: this.props.checked, onChange: this.onChange, disabled: this.props.disabled }), " ", this.props.pref, " "); } } class Personalization extends (external_React_default()).PureComponent { constructor(props) { super(props); this.togglePersonalization = this.togglePersonalization.bind(this); } togglePersonalization() { this.props.dispatch(actionCreators.OnlyToMain({ type: actionTypes.DISCOVERY_STREAM_PERSONALIZATION_TOGGLE })); } render() { const { lastUpdated, initialized } = this.props.state.Personalization; return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement("table", null, /*#__PURE__*/external_React_default().createElement("tbody", null, /*#__PURE__*/external_React_default().createElement(Row, null, /*#__PURE__*/external_React_default().createElement("td", { colSpan: "2" }, /*#__PURE__*/external_React_default().createElement(TogglePrefCheckbox, { checked: this.props.personalized, pref: "personalized", onChange: this.togglePersonalization }))), /*#__PURE__*/external_React_default().createElement(Row, null, /*#__PURE__*/external_React_default().createElement("td", { className: "min" }, "Personalization Last Updated"), /*#__PURE__*/external_React_default().createElement("td", null, relativeTime(lastUpdated) || "(no data)")), /*#__PURE__*/external_React_default().createElement(Row, null, /*#__PURE__*/external_React_default().createElement("td", { className: "min" }, "Personalization Initialized"), /*#__PURE__*/external_React_default().createElement("td", null, initialized ? "true" : "false"))))); } } class DiscoveryStreamAdmin extends (external_React_default()).PureComponent { constructor(props) { super(props); this.restorePrefDefaults = this.restorePrefDefaults.bind(this); this.setConfigValue = this.setConfigValue.bind(this); this.expireCache = this.expireCache.bind(this); this.refreshCache = this.refreshCache.bind(this); this.idleDaily = this.idleDaily.bind(this); this.systemTick = this.systemTick.bind(this); this.syncRemoteSettings = this.syncRemoteSettings.bind(this); this.changeEndpointVariant = this.changeEndpointVariant.bind(this); this.onStoryToggle = this.onStoryToggle.bind(this); this.state = { toggledStories: {} }; } setConfigValue(name, value) { this.props.dispatch(actionCreators.OnlyToMain({ type: actionTypes.DISCOVERY_STREAM_CONFIG_SET_VALUE, data: { name, value } })); } restorePrefDefaults(event) { this.props.dispatch(actionCreators.OnlyToMain({ type: actionTypes.DISCOVERY_STREAM_CONFIG_RESET_DEFAULTS })); } refreshCache() { const { config } = this.props.state.DiscoveryStream; this.props.dispatch(actionCreators.OnlyToMain({ type: actionTypes.DISCOVERY_STREAM_CONFIG_CHANGE, data: config })); } dispatchSimpleAction(type) { this.props.dispatch(actionCreators.OnlyToMain({ type })); } systemTick() { this.dispatchSimpleAction(actionTypes.DISCOVERY_STREAM_DEV_SYSTEM_TICK); } expireCache() { this.dispatchSimpleAction(actionTypes.DISCOVERY_STREAM_DEV_EXPIRE_CACHE); } idleDaily() { this.dispatchSimpleAction(actionTypes.DISCOVERY_STREAM_DEV_IDLE_DAILY); } syncRemoteSettings() { this.dispatchSimpleAction(actionTypes.DISCOVERY_STREAM_DEV_SYNC_RS); } changeEndpointVariant(event) { const endpoint = this.props.state.DiscoveryStream.config.layout_endpoint; if (endpoint) { this.setConfigValue("layout_endpoint", endpoint.replace(/layout_variant=.+/, `layout_variant=${event.target.value}`)); } } renderComponent(width, component) { return /*#__PURE__*/external_React_default().createElement("table", null, /*#__PURE__*/external_React_default().createElement("tbody", null, /*#__PURE__*/external_React_default().createElement(Row, null, /*#__PURE__*/external_React_default().createElement("td", { className: "min" }, "Type"), /*#__PURE__*/external_React_default().createElement("td", null, component.type)), /*#__PURE__*/external_React_default().createElement(Row, null, /*#__PURE__*/external_React_default().createElement("td", { className: "min" }, "Width"), /*#__PURE__*/external_React_default().createElement("td", null, width)), component.feed && this.renderFeed(component.feed))); } isCurrentVariant(id) { const endpoint = this.props.state.DiscoveryStream.config.layout_endpoint; const isMatch = endpoint && !!endpoint.match(`layout_variant=${id}`); return isMatch; } renderFeedData(url) { var _feed$recommendations; const { feeds } = this.props.state.DiscoveryStream; const feed = feeds.data[url].data; return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement("h4", null, "Feed url: ", url), /*#__PURE__*/external_React_default().createElement("table", null, /*#__PURE__*/external_React_default().createElement("tbody", null, (_feed$recommendations = feed.recommendations) === null || _feed$recommendations === void 0 ? void 0 : _feed$recommendations.map(story => this.renderStoryData(story))))); } renderFeedsData() { const { feeds } = this.props.state.DiscoveryStream; return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, Object.keys(feeds.data).map(url => this.renderFeedData(url))); } renderSpocs() { const { spocs } = this.props.state.DiscoveryStream; let spocsData = []; if (spocs.data && spocs.data.spocs && spocs.data.spocs.items) { spocsData = spocs.data.spocs.items || []; } return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement("table", null, /*#__PURE__*/external_React_default().createElement("tbody", null, /*#__PURE__*/external_React_default().createElement(Row, null, /*#__PURE__*/external_React_default().createElement("td", { className: "min" }, "spocs_endpoint"), /*#__PURE__*/external_React_default().createElement("td", null, spocs.spocs_endpoint)), /*#__PURE__*/external_React_default().createElement(Row, null, /*#__PURE__*/external_React_default().createElement("td", { className: "min" }, "Data last fetched"), /*#__PURE__*/external_React_default().createElement("td", null, relativeTime(spocs.lastUpdated))))), /*#__PURE__*/external_React_default().createElement("h4", null, "Spoc data"), /*#__PURE__*/external_React_default().createElement("table", null, /*#__PURE__*/external_React_default().createElement("tbody", null, spocsData.map(spoc => this.renderStoryData(spoc)))), /*#__PURE__*/external_React_default().createElement("h4", null, "Spoc frequency caps"), /*#__PURE__*/external_React_default().createElement("table", null, /*#__PURE__*/external_React_default().createElement("tbody", null, spocs.frequency_caps.map(spoc => this.renderStoryData(spoc))))); } onStoryToggle(story) { const { toggledStories } = this.state; this.setState({ toggledStories: { ...toggledStories, [story.id]: !toggledStories[story.id] } }); } renderStoryData(story) { let storyData = ""; if (this.state.toggledStories[story.id]) { storyData = JSON.stringify(story, null, 2); } return /*#__PURE__*/external_React_default().createElement("tr", { className: "message-item", key: story.id }, /*#__PURE__*/external_React_default().createElement("td", { className: "message-id" }, /*#__PURE__*/external_React_default().createElement("span", null, story.id, " ", /*#__PURE__*/external_React_default().createElement("br", null)), /*#__PURE__*/external_React_default().createElement(ToggleStoryButton, { story: story, onClick: this.onStoryToggle })), /*#__PURE__*/external_React_default().createElement("td", { className: "message-summary" }, /*#__PURE__*/external_React_default().createElement("pre", null, storyData))); } renderFeed(feed) { const { feeds } = this.props.state.DiscoveryStream; if (!feed.url) { return null; } return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement(Row, null, /*#__PURE__*/external_React_default().createElement("td", { className: "min" }, "Feed url"), /*#__PURE__*/external_React_default().createElement("td", null, feed.url)), /*#__PURE__*/external_React_default().createElement(Row, null, /*#__PURE__*/external_React_default().createElement("td", { className: "min" }, "Data last fetched"), /*#__PURE__*/external_React_default().createElement("td", null, relativeTime(feeds.data[feed.url] ? feeds.data[feed.url].lastUpdated : null) || "(no data)"))); } render() { const prefToggles = "enabled hardcoded_layout show_spocs collapsible".split(" "); const { config, lastUpdated, layout } = this.props.state.DiscoveryStream; const personalized = this.props.otherPrefs["discoverystream.personalization.enabled"]; return /*#__PURE__*/external_React_default().createElement("div", null, /*#__PURE__*/external_React_default().createElement("button", { className: "button", onClick: this.restorePrefDefaults }, "Restore Pref Defaults"), " ", /*#__PURE__*/external_React_default().createElement("button", { className: "button", onClick: this.refreshCache }, "Refresh Cache"), /*#__PURE__*/external_React_default().createElement("br", null), /*#__PURE__*/external_React_default().createElement("button", { className: "button", onClick: this.expireCache }, "Expire Cache"), " ", /*#__PURE__*/external_React_default().createElement("button", { className: "button", onClick: this.systemTick }, "Trigger System Tick"), " ", /*#__PURE__*/external_React_default().createElement("button", { className: "button", onClick: this.idleDaily }, "Trigger Idle Daily"), /*#__PURE__*/external_React_default().createElement("br", null), /*#__PURE__*/external_React_default().createElement("button", { className: "button", onClick: this.syncRemoteSettings }, "Sync Remote Settings"), /*#__PURE__*/external_React_default().createElement("table", null, /*#__PURE__*/external_React_default().createElement("tbody", null, prefToggles.map(pref => /*#__PURE__*/external_React_default().createElement(Row, { key: pref }, /*#__PURE__*/external_React_default().createElement("td", null, /*#__PURE__*/external_React_default().createElement(TogglePrefCheckbox, { checked: config[pref], pref: pref, onChange: this.setConfigValue })))))), /*#__PURE__*/external_React_default().createElement("h3", null, "Endpoint variant"), /*#__PURE__*/external_React_default().createElement("p", null, "You can also change this manually by changing this pref:", " ", /*#__PURE__*/external_React_default().createElement("code", null, "browser.newtabpage.activity-stream.discoverystream.config")), /*#__PURE__*/external_React_default().createElement("table", { style: config.enabled && !config.hardcoded_layout ? null : { opacity: 0.5 } }, /*#__PURE__*/external_React_default().createElement("tbody", null, Object.keys(LAYOUT_VARIANTS).map(id => /*#__PURE__*/external_React_default().createElement(Row, { key: id }, /*#__PURE__*/external_React_default().createElement("td", { className: "min" }, /*#__PURE__*/external_React_default().createElement("input", { type: "radio", value: id, checked: this.isCurrentVariant(id), onChange: this.changeEndpointVariant })), /*#__PURE__*/external_React_default().createElement("td", { className: "min" }, id), /*#__PURE__*/external_React_default().createElement("td", null, LAYOUT_VARIANTS[id]))))), /*#__PURE__*/external_React_default().createElement("h3", null, "Caching info"), /*#__PURE__*/external_React_default().createElement("table", { style: config.enabled ? null : { opacity: 0.5 } }, /*#__PURE__*/external_React_default().createElement("tbody", null, /*#__PURE__*/external_React_default().createElement(Row, null, /*#__PURE__*/external_React_default().createElement("td", { className: "min" }, "Data last fetched"), /*#__PURE__*/external_React_default().createElement("td", null, relativeTime(lastUpdated) || "(no data)")))), /*#__PURE__*/external_React_default().createElement("h3", null, "Layout"), layout.map((row, rowIndex) => /*#__PURE__*/external_React_default().createElement("div", { key: `row-${rowIndex}` }, row.components.map((component, componentIndex) => /*#__PURE__*/external_React_default().createElement("div", { key: `component-${componentIndex}`, className: "ds-component" }, this.renderComponent(row.width, component))))), /*#__PURE__*/external_React_default().createElement("h3", null, "Personalization"), /*#__PURE__*/external_React_default().createElement(Personalization, { personalized: personalized, dispatch: this.props.dispatch, state: { Personalization: this.props.state.Personalization } }), /*#__PURE__*/external_React_default().createElement("h3", null, "Spocs"), this.renderSpocs(), /*#__PURE__*/external_React_default().createElement("h3", null, "Feeds Data"), this.renderFeedsData()); } } class ASRouterAdminInner extends (external_React_default()).PureComponent { constructor(props) { super(props); this.handleEnabledToggle = this.handleEnabledToggle.bind(this); this.handleUserPrefToggle = this.handleUserPrefToggle.bind(this); this.onChangeMessageFilter = this.onChangeMessageFilter.bind(this); this.onChangeMessageGroupsFilter = this.onChangeMessageGroupsFilter.bind(this); this.unblockAll = this.unblockAll.bind(this); this.handleClearAllImpressionsByProvider = this.handleClearAllImpressionsByProvider.bind(this); this.handleExpressionEval = this.handleExpressionEval.bind(this); this.onChangeTargetingParameters = this.onChangeTargetingParameters.bind(this); this.onChangeAttributionParameters = this.onChangeAttributionParameters.bind(this); this.setAttribution = this.setAttribution.bind(this); this.onCopyTargetingParams = this.onCopyTargetingParams.bind(this); this.onNewTargetingParams = this.onNewTargetingParams.bind(this); this.handleOpenPB = this.handleOpenPB.bind(this); this.selectPBMessage = this.selectPBMessage.bind(this); this.resetPBJSON = this.resetPBJSON.bind(this); this.resetPBMessageState = this.resetPBMessageState.bind(this); this.toggleJSON = this.toggleJSON.bind(this); this.toggleAllMessages = this.toggleAllMessages.bind(this); this.resetGroups = this.resetGroups.bind(this); this.onMessageFromParent = this.onMessageFromParent.bind(this); this.setStateFromParent = this.setStateFromParent.bind(this); this.setState = this.setState.bind(this); this.state = { messageFilter: "all", messageGroupsFilter: "all", collapsedMessages: [], modifiedMessages: [], selectedPBMessage: "", evaluationStatus: {}, stringTargetingParameters: null, newStringTargetingParameters: null, copiedToClipboard: false, attributionParameters: { source: "addons.mozilla.org", medium: "referral", campaign: "non-fx-button", content: `rta:${btoa("uBlock0@raymondhill.net")}`, experiment: "ua-onboarding", variation: "chrome", ua: "Google Chrome 123", dltoken: "00000000-0000-0000-0000-000000000000" } }; } onMessageFromParent({ type, data }) { // These only exists due to onPrefChange events in ASRouter switch (type) { case "UpdateAdminState": { this.setStateFromParent(data); break; } } } setStateFromParent(data) { this.setState(data); if (!this.state.stringTargetingParameters) { const stringTargetingParameters = {}; for (const param of Object.keys(data.targetingParameters)) { stringTargetingParameters[param] = JSON.stringify(data.targetingParameters[param], null, 2); } this.setState({ stringTargetingParameters }); } } componentWillMount() { ASRouterUtils.addListener(this.onMessageFromParent); const endpoint = ASRouterUtils.getPreviewEndpoint(); ASRouterUtils.sendMessage({ type: "ADMIN_CONNECT_STATE", data: { endpoint } }).then(this.setStateFromParent); } handleBlock(msg) { return () => ASRouterUtils.blockById(msg.id); } handleUnblock(msg) { return () => ASRouterUtils.unblockById(msg.id); } resetJSON(msg) { // reset the displayed JSON for the given message document.getElementById(`${msg.id}-textarea`).value = JSON.stringify(msg, null, 2); // remove the message from the list of modified IDs let index = this.state.modifiedMessages.indexOf(msg.id); this.setState(prevState => ({ modifiedMessages: [...prevState.modifiedMessages.slice(0, index), ...prevState.modifiedMessages.slice(index + 1)] })); } handleOverride(id) { return () => ASRouterUtils.overrideMessage(id).then(state => { this.setStateFromParent(state); this.props.notifyContent({ message: state.message }); }); } resetPBMessageState() { // Iterate over Private Browsing messages and block/unblock each one to clear impressions const PBMessages = this.state.messages.filter(message => message.template === "pb_newtab"); // messages from state go here PBMessages.forEach(message => { if (message !== null && message !== void 0 && message.id) { ASRouterUtils.blockById(message.id); ASRouterUtils.unblockById(message.id); } }); // Clear the selected messages & radio buttons document.getElementById("clear radio").checked = true; this.selectPBMessage("clear"); } resetPBJSON(msg) { // reset the displayed JSON for the given message document.getElementById(`${msg.id}-textarea`).value = JSON.stringify(msg, null, 2); } handleOpenPB() { ASRouterUtils.sendMessage({ type: "FORCE_PRIVATE_BROWSING_WINDOW", data: { message: { content: this.state.selectedPBMessage } } }); } expireCache() { ASRouterUtils.sendMessage({ type: "EXPIRE_QUERY_CACHE" }); } resetPref() { ASRouterUtils.sendMessage({ type: "RESET_PROVIDER_PREF" }); } resetGroups(id, value) { ASRouterUtils.sendMessage({ type: "RESET_GROUPS_STATE" }).then(this.setStateFromParent); } handleExpressionEval() { const context = {}; for (const param of Object.keys(this.state.stringTargetingParameters)) { const value = this.state.stringTargetingParameters[param]; context[param] = value ? JSON.parse(value) : null; } ASRouterUtils.sendMessage({ type: "EVALUATE_JEXL_EXPRESSION", data: { expression: this.refs.expressionInput.value, context } }).then(this.setStateFromParent); } onChangeTargetingParameters(event) { const { name } = event.target; const { value } = event.target; this.setState(({ stringTargetingParameters }) => { let targetingParametersError = null; const updatedParameters = { ...stringTargetingParameters }; updatedParameters[name] = value; try { JSON.parse(value); } catch (e) { console.error(`Error parsing value of parameter ${name}`); targetingParametersError = { id: name }; } return { copiedToClipboard: false, evaluationStatus: {}, stringTargetingParameters: updatedParameters, targetingParametersError }; }); } unblockAll() { return ASRouterUtils.sendMessage({ type: "UNBLOCK_ALL" }).then(this.setStateFromParent); } handleClearAllImpressionsByProvider() { const providerId = this.state.messageFilter; if (!providerId) { return; } const userPrefInfo = this.state.userPrefs; const isUserEnabled = providerId in userPrefInfo ? userPrefInfo[providerId] : true; ASRouterUtils.sendMessage({ type: "DISABLE_PROVIDER", data: providerId }); if (!isUserEnabled) { ASRouterUtils.sendMessage({ type: "SET_PROVIDER_USER_PREF", data: { id: providerId, value: true } }); } ASRouterUtils.sendMessage({ type: "ENABLE_PROVIDER", data: providerId }); } handleEnabledToggle(event) { const provider = this.state.providerPrefs.find(p => p.id === event.target.dataset.provider); const userPrefInfo = this.state.userPrefs; const isUserEnabled = provider.id in userPrefInfo ? userPrefInfo[provider.id] : true; const isSystemEnabled = provider.enabled; const isEnabling = event.target.checked; if (isEnabling) { if (!isUserEnabled) { ASRouterUtils.sendMessage({ type: "SET_PROVIDER_USER_PREF", data: { id: provider.id, value: true } }); } if (!isSystemEnabled) { ASRouterUtils.sendMessage({ type: "ENABLE_PROVIDER", data: provider.id }); } } else { ASRouterUtils.sendMessage({ type: "DISABLE_PROVIDER", data: provider.id }); } this.setState({ messageFilter: "all" }); } handleUserPrefToggle(event) { const action = { type: "SET_PROVIDER_USER_PREF", data: { id: event.target.dataset.provider, value: event.target.checked } }; ASRouterUtils.sendMessage(action); this.setState({ messageFilter: "all" }); } onChangeMessageFilter(event) { this.setState({ messageFilter: event.target.value }); } onChangeMessageGroupsFilter(event) { this.setState({ messageGroupsFilter: event.target.value }); } // Simulate a copy event that sets to clipboard all targeting paramters and values onCopyTargetingParams(event) { const stringTargetingParameters = { ...this.state.stringTargetingParameters }; for (const key of Object.keys(stringTargetingParameters)) { // If the value is not set the parameter will be lost when we stringify if (stringTargetingParameters[key] === undefined) { stringTargetingParameters[key] = null; } } const setClipboardData = e => { e.preventDefault(); e.clipboardData.setData("text", JSON.stringify(stringTargetingParameters, null, 2)); document.removeEventListener("copy", setClipboardData); this.setState({ copiedToClipboard: true }); }; document.addEventListener("copy", setClipboardData); document.execCommand("copy"); } onNewTargetingParams(event) { this.setState({ newStringTargetingParameters: event.target.value }); event.target.classList.remove("errorState"); this.refs.targetingParamsEval.innerText = ""; try { const stringTargetingParameters = JSON.parse(event.target.value); this.setState({ stringTargetingParameters }); } catch (e) { event.target.classList.add("errorState"); this.refs.targetingParamsEval.innerText = e.message; } } toggleJSON(msgId) { if (this.state.collapsedMessages.includes(msgId)) { let index = this.state.collapsedMessages.indexOf(msgId); this.setState(prevState => ({ collapsedMessages: [...prevState.collapsedMessages.slice(0, index), ...prevState.collapsedMessages.slice(index + 1)] })); } else { this.setState(prevState => ({ collapsedMessages: prevState.collapsedMessages.concat(msgId) })); } } handleChange(msgId) { if (!this.state.modifiedMessages.includes(msgId)) { this.setState(prevState => ({ modifiedMessages: prevState.modifiedMessages.concat(msgId) })); } } renderMessageItem(msg) { const isBlockedByGroup = this.state.groups.filter(group => msg.groups.includes(group.id)).some(group => !group.enabled); const msgProvider = this.state.providers.find(provider => provider.id === msg.provider) || {}; const isProviderExcluded = msgProvider.exclude && msgProvider.exclude.includes(msg.id); const isMessageBlocked = this.state.messageBlockList.includes(msg.id) || this.state.messageBlockList.includes(msg.campaign); const isBlocked = isMessageBlocked || isBlockedByGroup || isProviderExcluded; const impressions = this.state.messageImpressions[msg.id] ? this.state.messageImpressions[msg.id].length : 0; const isCollapsed = this.state.collapsedMessages.includes(msg.id); const isModified = this.state.modifiedMessages.includes(msg.id); const aboutMessagePreviewSupported = ["infobar", "spotlight", "cfr_doorhanger"].includes(msg.template); let itemClassName = "message-item"; if (isBlocked) { itemClassName += " blocked"; } return /*#__PURE__*/external_React_default().createElement("tr", { className: itemClassName, key: `${msg.id}-${msg.provider}` }, /*#__PURE__*/external_React_default().createElement("td", { className: "message-id" }, /*#__PURE__*/external_React_default().createElement("span", null, msg.id, " ", /*#__PURE__*/external_React_default().createElement("br", null))), /*#__PURE__*/external_React_default().createElement("td", null, /*#__PURE__*/external_React_default().createElement(ToggleMessageJSON, { msgId: `${msg.id}`, toggleJSON: this.toggleJSON, isCollapsed: isCollapsed })), /*#__PURE__*/external_React_default().createElement("td", { className: "button-column" }, /*#__PURE__*/external_React_default().createElement("button", { className: `button ${isBlocked ? "" : " primary"}`, onClick: isBlocked ? this.handleUnblock(msg) : this.handleBlock(msg) }, isBlocked ? "Unblock" : "Block"), // eslint-disable-next-line no-nested-ternary isBlocked ? null : isModified ? /*#__PURE__*/external_React_default().createElement("button", { className: "button restore" // eslint-disable-next-line react/jsx-no-bind , onClick: e => this.resetJSON(msg) }, "Reset") : /*#__PURE__*/external_React_default().createElement("button", { className: "button show", onClick: this.handleOverride(msg.id) }, "Show"), isBlocked ? null : /*#__PURE__*/external_React_default().createElement("button", { className: "button modify" // eslint-disable-next-line react/jsx-no-bind , onClick: e => this.modifyJson(msg) }, "Modify"), aboutMessagePreviewSupported ? /*#__PURE__*/external_React_default().createElement(CopyButton, { transformer: text => `about:messagepreview?json=${encodeURIComponent(btoa(text))}`, label: "Share", copiedLabel: "Copied!", inputSelector: `#${msg.id}-textarea`, className: "button share" }) : null, /*#__PURE__*/external_React_default().createElement("br", null), "(", impressions, " impressions)"), /*#__PURE__*/external_React_default().createElement("td", { className: "message-summary" }, isBlocked && /*#__PURE__*/external_React_default().createElement("tr", null, "Block reason:", isBlockedByGroup && " Blocked by group", isProviderExcluded && " Excluded by provider", isMessageBlocked && " Message blocked"), /*#__PURE__*/external_React_default().createElement("tr", null, /*#__PURE__*/external_React_default().createElement("pre", { className: isCollapsed ? "collapsed" : "expanded" }, /*#__PURE__*/external_React_default().createElement("textarea", { id: `${msg.id}-textarea`, name: msg.id, className: "general-textarea", disabled: isBlocked // eslint-disable-next-line react/jsx-no-bind , onChange: e => this.handleChange(msg.id) }, JSON.stringify(msg, null, 2)))))); } selectPBMessage(msgId) { if (msgId === "clear") { this.setState({ selectedPBMessage: "" }); } else { let selected = document.getElementById(`${msgId} radio`); let msg = JSON.parse(document.getElementById(`${msgId}-textarea`).value); if (selected.checked) { this.setState({ selectedPBMessage: msg === null || msg === void 0 ? void 0 : msg.content }); } else { this.setState({ selectedPBMessage: "" }); } } } modifyJson(content) { const message = JSON.parse(document.getElementById(`${content.id}-textarea`).value); return ASRouterUtils.modifyMessageJson(message).then(state => { this.setStateFromParent(state); this.props.notifyContent({ message: state.message }); }); } renderPBMessageItem(msg) { const isBlocked = this.state.messageBlockList.includes(msg.id) || this.state.messageBlockList.includes(msg.campaign); const impressions = this.state.messageImpressions[msg.id] ? this.state.messageImpressions[msg.id].length : 0; const isCollapsed = this.state.collapsedMessages.includes(msg.id); let itemClassName = "message-item"; if (isBlocked) { itemClassName += " blocked"; } return /*#__PURE__*/external_React_default().createElement("tr", { className: itemClassName, key: `${msg.id}-${msg.provider}` }, /*#__PURE__*/external_React_default().createElement("td", { className: "message-id" }, /*#__PURE__*/external_React_default().createElement("span", null, msg.id, " ", /*#__PURE__*/external_React_default().createElement("br", null), /*#__PURE__*/external_React_default().createElement("br", null), "(", impressions, " impressions)")), /*#__PURE__*/external_React_default().createElement("td", null, /*#__PURE__*/external_React_default().createElement(ToggleMessageJSON, { msgId: `${msg.id}`, toggleJSON: this.toggleJSON, isCollapsed: isCollapsed })), /*#__PURE__*/external_React_default().createElement("td", null, /*#__PURE__*/external_React_default().createElement("input", { type: "radio", id: `${msg.id} radio`, name: "PB_message_radio", style: { marginBottom: 20 }, onClick: () => this.selectPBMessage(msg.id), disabled: isBlocked }), /*#__PURE__*/external_React_default().createElement("button", { className: `button ${isBlocked ? "" : " primary"}`, onClick: isBlocked ? this.handleUnblock(msg) : this.handleBlock(msg) }, isBlocked ? "Unblock" : "Block"), /*#__PURE__*/external_React_default().createElement("button", { className: "ASRouterButton slim button", onClick: e => this.resetPBJSON(msg) }, "Reset JSON")), /*#__PURE__*/external_React_default().createElement("td", { className: `message-summary` }, /*#__PURE__*/external_React_default().createElement("pre", { className: isCollapsed ? "collapsed" : "expanded" }, /*#__PURE__*/external_React_default().createElement("textarea", { id: `${msg.id}-textarea`, className: "wnp-textarea", name: msg.id }, JSON.stringify(msg, null, 2))))); } toggleAllMessages(messagesToShow) { if (this.state.collapsedMessages.length) { this.setState({ collapsedMessages: [] }); } else { Array.prototype.forEach.call(messagesToShow, msg => { this.setState(prevState => ({ collapsedMessages: prevState.collapsedMessages.concat(msg.id) })); }); } } renderMessages() { if (!this.state.messages) { return null; } const messagesToShow = this.state.messageFilter === "all" ? this.state.messages : this.state.messages.filter(message => message.provider === this.state.messageFilter && message.template !== "pb_newtab"); return /*#__PURE__*/external_React_default().createElement("div", null, /*#__PURE__*/external_React_default().createElement("button", { className: "ASRouterButton slim" // eslint-disable-next-line react/jsx-no-bind , onClick: e => this.toggleAllMessages(messagesToShow) }, "Collapse/Expand All"), /*#__PURE__*/external_React_default().createElement("p", { className: "helpLink" }, /*#__PURE__*/external_React_default().createElement("span", { className: "icon icon-small-spacer icon-info" }), " ", /*#__PURE__*/external_React_default().createElement("span", null, "To modify a message, change the JSON and click 'Modify' to see your changes. Click 'Reset' to restore the JSON to the original. Click 'Share' to copy a link to the clipboard that can be used to preview the message by opening the link in Nightly/local builds.")), /*#__PURE__*/external_React_default().createElement("table", null, /*#__PURE__*/external_React_default().createElement("tbody", null, messagesToShow.map(msg => this.renderMessageItem(msg))))); } renderMessagesByGroup() { if (!this.state.messages) { return null; } const messagesToShow = this.state.messageGroupsFilter === "all" ? this.state.messages.filter(m => m.groups.length) : this.state.messages.filter(message => message.groups.includes(this.state.messageGroupsFilter)); return /*#__PURE__*/external_React_default().createElement("table", null, /*#__PURE__*/external_React_default().createElement("tbody", null, messagesToShow.map(msg => this.renderMessageItem(msg)))); } renderPBMessages() { if (!this.state.messages) { return null; } const messagesToShow = this.state.messages.filter(message => message.template === "pb_newtab"); return /*#__PURE__*/external_React_default().createElement("table", null, /*#__PURE__*/external_React_default().createElement("tbody", null, messagesToShow.map(msg => this.renderPBMessageItem(msg)))); } renderMessageFilter() { if (!this.state.providers) { return null; } return /*#__PURE__*/external_React_default().createElement("p", null, /*#__PURE__*/external_React_default().createElement("button", { className: "unblock-all ASRouterButton test-only", onClick: this.unblockAll }, "Unblock All Snippets"), "Show messages from", " ", /*#__PURE__*/external_React_default().createElement("select", { value: this.state.messageFilter, onChange: this.onChangeMessageFilter }, /*#__PURE__*/external_React_default().createElement("option", { value: "all" }, "all providers"), this.state.providers.map(provider => /*#__PURE__*/external_React_default().createElement("option", { key: provider.id, value: provider.id }, provider.id))), this.state.messageFilter !== "all" && !this.state.messageFilter.includes("_local_testing") ? /*#__PURE__*/external_React_default().createElement("button", { className: "button messages-reset", onClick: this.handleClearAllImpressionsByProvider }, "Reset All") : null); } renderMessageGroupsFilter() { if (!this.state.groups) { return null; } return /*#__PURE__*/external_React_default().createElement("p", null, "Show messages from ", /*#__PURE__*/external_React_default().createElement("select", { value: this.state.messageGroupsFilter, onChange: this.onChangeMessageGroupsFilter }, /*#__PURE__*/external_React_default().createElement("option", { value: "all" }, "all groups"), this.state.groups.map(group => /*#__PURE__*/external_React_default().createElement("option", { key: group.id, value: group.id }, group.id)))); } renderTableHead() { return /*#__PURE__*/external_React_default().createElement("thead", null, /*#__PURE__*/external_React_default().createElement("tr", { className: "message-item" }, /*#__PURE__*/external_React_default().createElement("td", { className: "min" }), /*#__PURE__*/external_React_default().createElement("td", { className: "min" }, "Provider ID"), /*#__PURE__*/external_React_default().createElement("td", null, "Source"), /*#__PURE__*/external_React_default().createElement("td", { className: "min" }, "Cohort"), /*#__PURE__*/external_React_default().createElement("td", { className: "min" }, "Last Updated"))); } renderProviders() { const providersConfig = this.state.providerPrefs; const providerInfo = this.state.providers; const userPrefInfo = this.state.userPrefs; return /*#__PURE__*/external_React_default().createElement("table", null, this.renderTableHead(), /*#__PURE__*/external_React_default().createElement("tbody", null, providersConfig.map((provider, i) => { const isTestProvider = provider.id.includes("_local_testing"); const info = providerInfo.find(p => p.id === provider.id) || {}; const isUserEnabled = provider.id in userPrefInfo ? userPrefInfo[provider.id] : true; const isSystemEnabled = isTestProvider || provider.enabled; let label = "local"; if (provider.type === "remote") { label = /*#__PURE__*/external_React_default().createElement("span", null, "endpoint (", /*#__PURE__*/external_React_default().createElement("a", { className: "providerUrl", target: "_blank", href: info.url, rel: "noopener noreferrer" }, info.url), ")"); } else if (provider.type === "remote-settings") { label = `remote settings (${provider.collection})`; } else if (provider.type === "remote-experiments") { label = /*#__PURE__*/external_React_default().createElement("span", null, "remote settings (", /*#__PURE__*/external_React_default().createElement("a", { className: "providerUrl", target: "_blank", href: "https://firefox.settings.services.mozilla.com/v1/buckets/main/collections/nimbus-desktop-experiments/records", rel: "noopener noreferrer" }, "nimbus-desktop-experiments"), ")"); } let reasonsDisabled = []; if (!isSystemEnabled) { reasonsDisabled.push("system pref"); } if (!isUserEnabled) { reasonsDisabled.push("user pref"); } if (reasonsDisabled.length) { label = `disabled via ${reasonsDisabled.join(", ")}`; } return /*#__PURE__*/external_React_default().createElement("tr", { className: "message-item", key: i }, /*#__PURE__*/external_React_default().createElement("td", null, isTestProvider ? /*#__PURE__*/external_React_default().createElement("input", { type: "checkbox", disabled: true, readOnly: true, checked: true }) : /*#__PURE__*/external_React_default().createElement("input", { type: "checkbox", "data-provider": provider.id, checked: isUserEnabled && isSystemEnabled, onChange: this.handleEnabledToggle })), /*#__PURE__*/external_React_default().createElement("td", null, provider.id), /*#__PURE__*/external_React_default().createElement("td", null, /*#__PURE__*/external_React_default().createElement("span", { className: `sourceLabel${isUserEnabled && isSystemEnabled ? "" : " isDisabled"}` }, label)), /*#__PURE__*/external_React_default().createElement("td", null, provider.cohort), /*#__PURE__*/external_React_default().createElement("td", { style: { whiteSpace: "nowrap" } }, info.lastUpdated ? new Date(info.lastUpdated).toLocaleString() : "")); }))); } renderTargetingParameters() { // There was no error and the result is truthy const success = this.state.evaluationStatus.success && !!this.state.evaluationStatus.result; const result = JSON.stringify(this.state.evaluationStatus.result, null, 2) || "(Empty result)"; return /*#__PURE__*/external_React_default().createElement("table", null, /*#__PURE__*/external_React_default().createElement("tbody", null, /*#__PURE__*/external_React_default().createElement("tr", null, /*#__PURE__*/external_React_default().createElement("td", null, /*#__PURE__*/external_React_default().createElement("h2", null, "Evaluate JEXL expression"))), /*#__PURE__*/external_React_default().createElement("tr", null, /*#__PURE__*/external_React_default().createElement("td", null, /*#__PURE__*/external_React_default().createElement("p", null, /*#__PURE__*/external_React_default().createElement("textarea", { ref: "expressionInput", rows: "10", cols: "60", placeholder: "Evaluate JEXL expressions and mock parameters by changing their values below" })), /*#__PURE__*/external_React_default().createElement("p", null, "Status:", " ", /*#__PURE__*/external_React_default().createElement("span", { ref: "evaluationStatus" }, success ? "✅" : "❌", ", Result: ", result))), /*#__PURE__*/external_React_default().createElement("td", null, /*#__PURE__*/external_React_default().createElement("button", { className: "ASRouterButton secondary", onClick: this.handleExpressionEval }, "Evaluate"))), /*#__PURE__*/external_React_default().createElement("tr", null, /*#__PURE__*/external_React_default().createElement("td", null, /*#__PURE__*/external_React_default().createElement("h2", null, "Modify targeting parameters"))), /*#__PURE__*/external_React_default().createElement("tr", null, /*#__PURE__*/external_React_default().createElement("td", null, /*#__PURE__*/external_React_default().createElement("button", { className: "ASRouterButton secondary", onClick: this.onCopyTargetingParams, disabled: this.state.copiedToClipboard }, this.state.copiedToClipboard ? "Parameters copied!" : "Copy parameters"))), this.state.stringTargetingParameters && Object.keys(this.state.stringTargetingParameters).map((param, i) => { const value = this.state.stringTargetingParameters[param]; const errorState = this.state.targetingParametersError && this.state.targetingParametersError.id === param; const className = errorState ? "errorState" : ""; const inputComp = (value && value.length) > 30 ? /*#__PURE__*/external_React_default().createElement("textarea", { name: param, className: className, value: value, rows: "10", cols: "60", onChange: this.onChangeTargetingParameters }) : /*#__PURE__*/external_React_default().createElement("input", { name: param, className: className, value: value, onChange: this.onChangeTargetingParameters }); return /*#__PURE__*/external_React_default().createElement("tr", { key: i }, /*#__PURE__*/external_React_default().createElement("td", null, param), /*#__PURE__*/external_React_default().createElement("td", null, inputComp)); }))); } onChangeAttributionParameters(event) { const { name, value } = event.target; this.setState(({ attributionParameters }) => { const updatedParameters = { ...attributionParameters }; updatedParameters[name] = value; return { attributionParameters: updatedParameters }; }); } setAttribution(e) { ASRouterUtils.sendMessage({ type: "FORCE_ATTRIBUTION", data: this.state.attributionParameters }).then(this.setStateFromParent); } _getGroupImpressionsCount(id, frequency) { if (frequency) { return this.state.groupImpressions[id] ? this.state.groupImpressions[id].length : 0; } return "n/a"; } renderDiscoveryStream() { const { config } = this.props.DiscoveryStream; return /*#__PURE__*/external_React_default().createElement("div", null, /*#__PURE__*/external_React_default().createElement("table", null, /*#__PURE__*/external_React_default().createElement("tbody", null, /*#__PURE__*/external_React_default().createElement("tr", { className: "message-item" }, /*#__PURE__*/external_React_default().createElement("td", { className: "min" }, "Enabled"), /*#__PURE__*/external_React_default().createElement("td", null, config.enabled ? "yes" : "no")), /*#__PURE__*/external_React_default().createElement("tr", { className: "message-item" }, /*#__PURE__*/external_React_default().createElement("td", { className: "min" }, "Endpoint"), /*#__PURE__*/external_React_default().createElement("td", null, config.endpoint || "(empty)"))))); } renderAttributionParamers() { return /*#__PURE__*/external_React_default().createElement("div", null, /*#__PURE__*/external_React_default().createElement("h2", null, " Attribution Parameters "), /*#__PURE__*/external_React_default().createElement("p", null, " ", "This forces the browser to set some attribution parameters, useful for testing the Return To AMO feature. Clicking on 'Force Attribution', with the default values in each field, will demo the Return To AMO flow with the addon called 'uBlock Origin'. If you wish to try different attribution parameters, enter them in the text boxes. If you wish to try a different addon with the Return To AMO flow, make sure the 'content' text box has a string that is 'rta:base64(addonID)', the base64 string of the addonID prefixed with 'rta:'. The addon must currently be a recommended addon on AMO. Then click 'Force Attribution'. Clicking on 'Force Attribution' with blank text boxes reset attribution data."), /*#__PURE__*/external_React_default().createElement("table", null, /*#__PURE__*/external_React_default().createElement("tr", null, /*#__PURE__*/external_React_default().createElement("td", null, /*#__PURE__*/external_React_default().createElement("b", null, " Source ")), /*#__PURE__*/external_React_default().createElement("td", null, " ", /*#__PURE__*/external_React_default().createElement("input", { type: "text", name: "source", placeholder: "addons.mozilla.org", value: this.state.attributionParameters.source, onChange: this.onChangeAttributionParameters }), " ")), /*#__PURE__*/external_React_default().createElement("tr", null, /*#__PURE__*/external_React_default().createElement("td", null, /*#__PURE__*/external_React_default().createElement("b", null, " Medium ")), /*#__PURE__*/external_React_default().createElement("td", null, " ", /*#__PURE__*/external_React_default().createElement("input", { type: "text", name: "medium", placeholder: "referral", value: this.state.attributionParameters.medium, onChange: this.onChangeAttributionParameters }), " ")), /*#__PURE__*/external_React_default().createElement("tr", null, /*#__PURE__*/external_React_default().createElement("td", null, /*#__PURE__*/external_React_default().createElement("b", null, " Campaign ")), /*#__PURE__*/external_React_default().createElement("td", null, " ", /*#__PURE__*/external_React_default().createElement("input", { type: "text", name: "campaign", placeholder: "non-fx-button", value: this.state.attributionParameters.campaign, onChange: this.onChangeAttributionParameters }), " ")), /*#__PURE__*/external_React_default().createElement("tr", null, /*#__PURE__*/external_React_default().createElement("td", null, /*#__PURE__*/external_React_default().createElement("b", null, " Content ")), /*#__PURE__*/external_React_default().createElement("td", null, " ", /*#__PURE__*/external_React_default().createElement("input", { type: "text", name: "content", placeholder: `rta:${btoa("uBlock0@raymondhill.net")}`, value: this.state.attributionParameters.content, onChange: this.onChangeAttributionParameters }), " ")), /*#__PURE__*/external_React_default().createElement("tr", null, /*#__PURE__*/external_React_default().createElement("td", null, /*#__PURE__*/external_React_default().createElement("b", null, " Experiment ")), /*#__PURE__*/external_React_default().createElement("td", null, " ", /*#__PURE__*/external_React_default().createElement("input", { type: "text", name: "experiment", placeholder: "ua-onboarding", value: this.state.attributionParameters.experiment, onChange: this.onChangeAttributionParameters }), " ")), /*#__PURE__*/external_React_default().createElement("tr", null, /*#__PURE__*/external_React_default().createElement("td", null, /*#__PURE__*/external_React_default().createElement("b", null, " Variation ")), /*#__PURE__*/external_React_default().createElement("td", null, " ", /*#__PURE__*/external_React_default().createElement("input", { type: "text", name: "variation", placeholder: "chrome", value: this.state.attributionParameters.variation, onChange: this.onChangeAttributionParameters }), " ")), /*#__PURE__*/external_React_default().createElement("tr", null, /*#__PURE__*/external_React_default().createElement("td", null, /*#__PURE__*/external_React_default().createElement("b", null, " User Agent ")), /*#__PURE__*/external_React_default().createElement("td", null, " ", /*#__PURE__*/external_React_default().createElement("input", { type: "text", name: "ua", placeholder: "Google Chrome 123", value: this.state.attributionParameters.ua, onChange: this.onChangeAttributionParameters }), " ")), /*#__PURE__*/external_React_default().createElement("tr", null, /*#__PURE__*/external_React_default().createElement("td", null, /*#__PURE__*/external_React_default().createElement("b", null, " Download Token ")), /*#__PURE__*/external_React_default().createElement("td", null, " ", /*#__PURE__*/external_React_default().createElement("input", { type: "text", name: "dltoken", placeholder: "00000000-0000-0000-0000-000000000000", value: this.state.attributionParameters.dltoken, onChange: this.onChangeAttributionParameters }), " ")), /*#__PURE__*/external_React_default().createElement("tr", null, /*#__PURE__*/external_React_default().createElement("td", null, " ", /*#__PURE__*/external_React_default().createElement("button", { className: "ASRouterButton primary button", onClick: this.setAttribution }, " ", "Force Attribution", " "), " ")))); } renderErrorMessage({ id, errors }) { const providerId = /*#__PURE__*/external_React_default().createElement("td", { rowSpan: errors.length }, id); // .reverse() so that the last error (most recent) is first return errors.map(({ error, timestamp }, cellKey) => /*#__PURE__*/external_React_default().createElement("tr", { key: cellKey }, cellKey === errors.length - 1 ? providerId : null, /*#__PURE__*/external_React_default().createElement("td", null, error.message), /*#__PURE__*/external_React_default().createElement("td", null, relativeTime(timestamp)))).reverse(); } renderErrors() { const providersWithErrors = this.state.providers && this.state.providers.filter(p => p.errors && p.errors.length); if (providersWithErrors && providersWithErrors.length) { return /*#__PURE__*/external_React_default().createElement("table", { className: "errorReporting" }, /*#__PURE__*/external_React_default().createElement("thead", null, /*#__PURE__*/external_React_default().createElement("tr", null, /*#__PURE__*/external_React_default().createElement("th", null, "Provider ID"), /*#__PURE__*/external_React_default().createElement("th", null, "Message"), /*#__PURE__*/external_React_default().createElement("th", null, "Timestamp"))), /*#__PURE__*/external_React_default().createElement("tbody", null, providersWithErrors.map(this.renderErrorMessage))); } return /*#__PURE__*/external_React_default().createElement("p", null, "No errors"); } renderPBTab() { if (!this.state.messages) { return null; } let messagesToShow = this.state.messages.filter(message => message.template === "pb_newtab"); return /*#__PURE__*/external_React_default().createElement("div", null, /*#__PURE__*/external_React_default().createElement("p", { className: "helpLink" }, /*#__PURE__*/external_React_default().createElement("span", { className: "icon icon-small-spacer icon-info" }), " ", /*#__PURE__*/external_React_default().createElement("span", null, "To view an available message, select its radio button and click \"Open a Private Browsing Window\".", /*#__PURE__*/external_React_default().createElement("br", null), "To modify a message, make changes to the JSON first, then select the radio button. (To make new changes, click \"Reset Message State\", make your changes, and reselect the radio button.)", /*#__PURE__*/external_React_default().createElement("br", null), "Click \"Reset Message State\" to clear all message impressions and view messages in a clean state.", /*#__PURE__*/external_React_default().createElement("br", null), "Note that ContentSearch functions do not work in debug mode.")), /*#__PURE__*/external_React_default().createElement("div", null, /*#__PURE__*/external_React_default().createElement("button", { className: "ASRouterButton primary button", onClick: this.handleOpenPB }, "Open a Private Browsing Window"), /*#__PURE__*/external_React_default().createElement("button", { className: "ASRouterButton primary button", style: { marginInlineStart: 12 }, onClick: this.resetPBMessageState }, "Reset Message State"), /*#__PURE__*/external_React_default().createElement("br", null), /*#__PURE__*/external_React_default().createElement("input", { type: "radio", id: `clear radio`, name: "PB_message_radio", value: "clearPBMessage", style: { display: "none" } }), /*#__PURE__*/external_React_default().createElement("h2", null, "Messages"), /*#__PURE__*/external_React_default().createElement("button", { className: "ASRouterButton slim button" // eslint-disable-next-line react/jsx-no-bind , onClick: e => this.toggleAllMessages(messagesToShow) }, "Collapse/Expand All"), this.renderPBMessages())); } getSection() { const [section] = this.props.location.routes; switch (section) { case "private": return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement("h2", null, "Private Browsing Messages"), this.renderPBTab()); case "targeting": return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement("h2", null, "Targeting Utilities"), /*#__PURE__*/external_React_default().createElement("button", { className: "button", onClick: this.expireCache }, "Expire Cache"), " ", "(This expires the cache in ASR Targeting for bookmarks and top sites)", this.renderTargetingParameters(), this.renderAttributionParamers()); case "groups": return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement("h2", null, "Message Groups"), /*#__PURE__*/external_React_default().createElement("button", { className: "button", onClick: this.resetGroups }, "Reset group impressions"), /*#__PURE__*/external_React_default().createElement("table", null, /*#__PURE__*/external_React_default().createElement("thead", null, /*#__PURE__*/external_React_default().createElement("tr", { className: "message-item" }, /*#__PURE__*/external_React_default().createElement("td", null, "Enabled"), /*#__PURE__*/external_React_default().createElement("td", null, "Impressions count"), /*#__PURE__*/external_React_default().createElement("td", null, "Custom frequency"), /*#__PURE__*/external_React_default().createElement("td", null, "User preferences"))), /*#__PURE__*/external_React_default().createElement("tbody", null, this.state.groups && this.state.groups.map(({ id, enabled, frequency, userPreferences = [] }, index) => /*#__PURE__*/external_React_default().createElement(Row, { key: id }, /*#__PURE__*/external_React_default().createElement("td", null, /*#__PURE__*/external_React_default().createElement(TogglePrefCheckbox, { checked: enabled, pref: id, disabled: true })), /*#__PURE__*/external_React_default().createElement("td", null, this._getGroupImpressionsCount(id, frequency)), /*#__PURE__*/external_React_default().createElement("td", null, JSON.stringify(frequency, null, 2)), /*#__PURE__*/external_React_default().createElement("td", null, userPreferences.join(", ")))))), this.renderMessageGroupsFilter(), this.renderMessagesByGroup()); case "ds": return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement("h2", null, "Discovery Stream"), /*#__PURE__*/external_React_default().createElement(DiscoveryStreamAdmin, { state: { DiscoveryStream: this.props.DiscoveryStream, Personalization: this.props.Personalization }, otherPrefs: this.props.Prefs.values, dispatch: this.props.dispatch })); case "errors": return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement("h2", null, "ASRouter Errors"), this.renderErrors()); default: return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement("h2", null, "Message Providers", " ", /*#__PURE__*/external_React_default().createElement("button", { title: "Restore all provider settings that ship with Firefox", className: "button", onClick: this.resetPref }, "Restore default prefs")), this.state.providers ? this.renderProviders() : null, /*#__PURE__*/external_React_default().createElement("h2", null, "Messages"), this.renderMessageFilter(), this.renderMessages()); } } render() { return /*#__PURE__*/external_React_default().createElement("div", { className: `asrouter-admin ${this.props.collapsed ? "collapsed" : "expanded"}` }, /*#__PURE__*/external_React_default().createElement("aside", { className: "sidebar" }, /*#__PURE__*/external_React_default().createElement("ul", null, /*#__PURE__*/external_React_default().createElement("li", null, /*#__PURE__*/external_React_default().createElement("a", { href: "#devtools" }, "General")), /*#__PURE__*/external_React_default().createElement("li", null, /*#__PURE__*/external_React_default().createElement("a", { href: "#devtools-private" }, "Private Browsing")), /*#__PURE__*/external_React_default().createElement("li", null, /*#__PURE__*/external_React_default().createElement("a", { href: "#devtools-targeting" }, "Targeting")), /*#__PURE__*/external_React_default().createElement("li", null, /*#__PURE__*/external_React_default().createElement("a", { href: "#devtools-groups" }, "Message Groups")), /*#__PURE__*/external_React_default().createElement("li", null, /*#__PURE__*/external_React_default().createElement("a", { href: "#devtools-ds" }, "Discovery Stream")), /*#__PURE__*/external_React_default().createElement("li", null, /*#__PURE__*/external_React_default().createElement("a", { href: "#devtools-errors" }, "Errors")))), /*#__PURE__*/external_React_default().createElement("main", { className: "main-panel" }, /*#__PURE__*/external_React_default().createElement("h1", null, "AS Router Admin"), /*#__PURE__*/external_React_default().createElement("p", { className: "helpLink" }, /*#__PURE__*/external_React_default().createElement("span", { className: "icon icon-small-spacer icon-info" }), " ", /*#__PURE__*/external_React_default().createElement("span", null, "Need help using these tools? Check out our", " ", /*#__PURE__*/external_React_default().createElement("a", { target: "blank", href: "https://firefox-source-docs.mozilla.org/browser/components/newtab/content-src/asrouter/docs/debugging-docs.html" }, "documentation"))), this.getSection())); } } class CollapseToggle extends (external_React_default()).PureComponent { constructor(props) { super(props); this.onCollapseToggle = this.onCollapseToggle.bind(this); this.state = { collapsed: false }; } get renderAdmin() { const { props } = this; return props.location.hash && (props.location.hash.startsWith("#asrouter") || props.location.hash.startsWith("#devtools")); } onCollapseToggle(e) { e.preventDefault(); this.setState(state => ({ collapsed: !state.collapsed })); } setBodyClass() { if (this.renderAdmin && !this.state.collapsed) { __webpack_require__.g.document.body.classList.add("no-scroll"); } else { __webpack_require__.g.document.body.classList.remove("no-scroll"); } } componentDidMount() { this.setBodyClass(); } componentDidUpdate() { this.setBodyClass(); } componentWillUnmount() { __webpack_require__.g.document.body.classList.remove("no-scroll"); ASRouterUtils.removeListener(this.onMessageFromParent); } render() { const { props } = this; const { renderAdmin } = this; const isCollapsed = this.state.collapsed || !renderAdmin; const label = `${isCollapsed ? "Expand" : "Collapse"} devtools`; return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement("a", { href: "#devtools", title: label, "aria-label": label, className: `asrouter-toggle ${isCollapsed ? "collapsed" : "expanded"}`, onClick: this.renderAdmin ? this.onCollapseToggle : null }, /*#__PURE__*/external_React_default().createElement("span", { className: "icon icon-devtools" })), renderAdmin ? /*#__PURE__*/external_React_default().createElement(ASRouterAdminInner, ASRouterAdmin_extends({}, props, { collapsed: this.state.collapsed })) : null); } } const _ASRouterAdmin = props => /*#__PURE__*/external_React_default().createElement(SimpleHashRouter, null, /*#__PURE__*/external_React_default().createElement(CollapseToggle, props)); const ASRouterAdmin = (0,external_ReactRedux_namespaceObject.connect)(state => ({ Sections: state.Sections, DiscoveryStream: state.DiscoveryStream, Personalization: state.Personalization, Prefs: state.Prefs }))(_ASRouterAdmin); ;// CONCATENATED MODULE: ./node_modules/@fluent/bundle/esm/types.js /** * The `FluentType` class is the base of Fluent's type system. * * Fluent types wrap JavaScript values and store additional configuration for * them, which can then be used in the `toString` method together with a proper * `Intl` formatter. */ class FluentType { /** * Create a `FluentType` instance. * * @param value The JavaScript value to wrap. */ constructor(value) { this.value = value; } /** * Unwrap the raw value stored by this `FluentType`. */ valueOf() { return this.value; } } /** * A `FluentType` representing no correct value. */ class FluentNone extends FluentType { /** * Create an instance of `FluentNone` with an optional fallback value. * @param value The fallback value of this `FluentNone`. */ constructor(value = "???") { super(value); } /** * Format this `FluentNone` to the fallback string. */ toString(scope) { return `{${this.value}}`; } } /** * A `FluentType` representing a number. * * A `FluentNumber` instance stores the number value of the number it * represents. It may also store an option bag of options which will be passed * to `Intl.NumerFormat` when the `FluentNumber` is formatted to a string. */ class FluentNumber extends FluentType { /** * Create an instance of `FluentNumber` with options to the * `Intl.NumberFormat` constructor. * * @param value The number value of this `FluentNumber`. * @param opts Options which will be passed to `Intl.NumberFormat`. */ constructor(value, opts = {}) { super(value); this.opts = opts; } /** * Format this `FluentNumber` to a string. */ toString(scope) { try { const nf = scope.memoizeIntlObject(Intl.NumberFormat, this.opts); return nf.format(this.value); } catch (err) { scope.reportError(err); return this.value.toString(10); } } } /** * A `FluentType` representing a date and time. * * A `FluentDateTime` instance stores the number value of the date it * represents, as a numerical timestamp in milliseconds. It may also store an * option bag of options which will be passed to `Intl.DateTimeFormat` when the * `FluentDateTime` is formatted to a string. */ class FluentDateTime extends FluentType { /** * Create an instance of `FluentDateTime` with options to the * `Intl.DateTimeFormat` constructor. * * @param value The number value of this `FluentDateTime`, in milliseconds. * @param opts Options which will be passed to `Intl.DateTimeFormat`. */ constructor(value, opts = {}) { super(value); this.opts = opts; } /** * Format this `FluentDateTime` to a string. */ toString(scope) { try { const dtf = scope.memoizeIntlObject(Intl.DateTimeFormat, this.opts); return dtf.format(this.value); } catch (err) { scope.reportError(err); return new Date(this.value).toISOString(); } } } ;// CONCATENATED MODULE: ./node_modules/@fluent/bundle/esm/resolver.js /* global Intl */ /** * @overview * * The role of the Fluent resolver is to format a `Pattern` to an instance of * `FluentValue`. For performance reasons, primitive strings are considered * such instances, too. * * Translations can contain references to other messages or variables, * conditional logic in form of select expressions, traits which describe their * grammatical features, and can use Fluent builtins which make use of the * `Intl` formatters to format numbers and dates into the bundle's languages. * See the documentation of the Fluent syntax for more information. * * In case of errors the resolver will try to salvage as much of the * translation as possible. In rare situations where the resolver didn't know * how to recover from an error it will return an instance of `FluentNone`. * * All expressions resolve to an instance of `FluentValue`. The caller should * use the `toString` method to convert the instance to a native value. * * Functions in this file pass around an instance of the `Scope` class, which * stores the data required for successful resolution and error recovery. */ // The maximum number of placeables which can be expanded in a single call to // `formatPattern`. The limit protects against the Billion Laughs and Quadratic // Blowup attacks. See https://msdn.microsoft.com/en-us/magazine/ee335713.aspx. const MAX_PLACEABLES = 100; // Unicode bidi isolation characters. const FSI = "\u2068"; const PDI = "\u2069"; // Helper: match a variant key to the given selector. function match(scope, selector, key) { if (key === selector) { // Both are strings. return true; } // XXX Consider comparing options too, e.g. minimumFractionDigits. if (key instanceof FluentNumber && selector instanceof FluentNumber && key.value === selector.value) { return true; } if (selector instanceof FluentNumber && typeof key === "string") { let category = scope.memoizeIntlObject(Intl.PluralRules, selector.opts).select(selector.value); if (key === category) { return true; } } return false; } // Helper: resolve the default variant from a list of variants. function getDefault(scope, variants, star) { if (variants[star]) { return resolvePattern(scope, variants[star].value); } scope.reportError(new RangeError("No default")); return new FluentNone(); } // Helper: resolve arguments to a call expression. function getArguments(scope, args) { const positional = []; const named = Object.create(null); for (const arg of args) { if (arg.type === "narg") { named[arg.name] = resolveExpression(scope, arg.value); } else { positional.push(resolveExpression(scope, arg)); } } return { positional, named }; } // Resolve an expression to a Fluent type. function resolveExpression(scope, expr) { switch (expr.type) { case "str": return expr.value; case "num": return new FluentNumber(expr.value, { minimumFractionDigits: expr.precision }); case "var": return resolveVariableReference(scope, expr); case "mesg": return resolveMessageReference(scope, expr); case "term": return resolveTermReference(scope, expr); case "func": return resolveFunctionReference(scope, expr); case "select": return resolveSelectExpression(scope, expr); default: return new FluentNone(); } } // Resolve a reference to a variable. function resolveVariableReference(scope, { name }) { let arg; if (scope.params) { // We're inside a TermReference. It's OK to reference undefined parameters. if (Object.prototype.hasOwnProperty.call(scope.params, name)) { arg = scope.params[name]; } else { return new FluentNone(`$${name}`); } } else if (scope.args && Object.prototype.hasOwnProperty.call(scope.args, name)) { // We're in the top-level Pattern or inside a MessageReference. Missing // variables references produce ReferenceErrors. arg = scope.args[name]; } else { scope.reportError(new ReferenceError(`Unknown variable: $${name}`)); return new FluentNone(`$${name}`); } // Return early if the argument already is an instance of FluentType. if (arg instanceof FluentType) { return arg; } // Convert the argument to a Fluent type. switch (typeof arg) { case "string": return arg; case "number": return new FluentNumber(arg); case "object": if (arg instanceof Date) { return new FluentDateTime(arg.getTime()); } // eslint-disable-next-line no-fallthrough default: scope.reportError(new TypeError(`Variable type not supported: $${name}, ${typeof arg}`)); return new FluentNone(`$${name}`); } } // Resolve a reference to another message. function resolveMessageReference(scope, { name, attr }) { const message = scope.bundle._messages.get(name); if (!message) { scope.reportError(new ReferenceError(`Unknown message: ${name}`)); return new FluentNone(name); } if (attr) { const attribute = message.attributes[attr]; if (attribute) { return resolvePattern(scope, attribute); } scope.reportError(new ReferenceError(`Unknown attribute: ${attr}`)); return new FluentNone(`${name}.${attr}`); } if (message.value) { return resolvePattern(scope, message.value); } scope.reportError(new ReferenceError(`No value: ${name}`)); return new FluentNone(name); } // Resolve a call to a Term with key-value arguments. function resolveTermReference(scope, { name, attr, args }) { const id = `-${name}`; const term = scope.bundle._terms.get(id); if (!term) { scope.reportError(new ReferenceError(`Unknown term: ${id}`)); return new FluentNone(id); } if (attr) { const attribute = term.attributes[attr]; if (attribute) { // Every TermReference has its own variables. scope.params = getArguments(scope, args).named; const resolved = resolvePattern(scope, attribute); scope.params = null; return resolved; } scope.reportError(new ReferenceError(`Unknown attribute: ${attr}`)); return new FluentNone(`${id}.${attr}`); } scope.params = getArguments(scope, args).named; const resolved = resolvePattern(scope, term.value); scope.params = null; return resolved; } // Resolve a call to a Function with positional and key-value arguments. function resolveFunctionReference(scope, { name, args }) { // Some functions are built-in. Others may be provided by the runtime via // the `FluentBundle` constructor. let func = scope.bundle._functions[name]; if (!func) { scope.reportError(new ReferenceError(`Unknown function: ${name}()`)); return new FluentNone(`${name}()`); } if (typeof func !== "function") { scope.reportError(new TypeError(`Function ${name}() is not callable`)); return new FluentNone(`${name}()`); } try { let resolved = getArguments(scope, args); return func(resolved.positional, resolved.named); } catch (err) { scope.reportError(err); return new FluentNone(`${name}()`); } } // Resolve a select expression to the member object. function resolveSelectExpression(scope, { selector, variants, star }) { let sel = resolveExpression(scope, selector); if (sel instanceof FluentNone) { return getDefault(scope, variants, star); } // Match the selector against keys of each variant, in order. for (const variant of variants) { const key = resolveExpression(scope, variant.key); if (match(scope, sel, key)) { return resolvePattern(scope, variant.value); } } return getDefault(scope, variants, star); } // Resolve a pattern (a complex string with placeables). function resolveComplexPattern(scope, ptn) { if (scope.dirty.has(ptn)) { scope.reportError(new RangeError("Cyclic reference")); return new FluentNone(); } // Tag the pattern as dirty for the purpose of the current resolution. scope.dirty.add(ptn); const result = []; // Wrap interpolations with Directional Isolate Formatting characters // only when the pattern has more than one element. const useIsolating = scope.bundle._useIsolating && ptn.length > 1; for (const elem of ptn) { if (typeof elem === "string") { result.push(scope.bundle._transform(elem)); continue; } scope.placeables++; if (scope.placeables > MAX_PLACEABLES) { scope.dirty.delete(ptn); // This is a fatal error which causes the resolver to instantly bail out // on this pattern. The length check protects against excessive memory // usage, and throwing protects against eating up the CPU when long // placeables are deeply nested. throw new RangeError(`Too many placeables expanded: ${scope.placeables}, ` + `max allowed is ${MAX_PLACEABLES}`); } if (useIsolating) { result.push(FSI); } result.push(resolveExpression(scope, elem).toString(scope)); if (useIsolating) { result.push(PDI); } } scope.dirty.delete(ptn); return result.join(""); } // Resolve a simple or a complex Pattern to a FluentString (which is really the // string primitive). function resolvePattern(scope, value) { // Resolve a simple pattern. if (typeof value === "string") { return scope.bundle._transform(value); } return resolveComplexPattern(scope, value); } ;// CONCATENATED MODULE: ./node_modules/@fluent/bundle/esm/scope.js class Scope { constructor(bundle, errors, args) { /** The Set of patterns already encountered during this resolution. * Used to detect and prevent cyclic resolutions. */ this.dirty = new WeakSet(); /** A dict of parameters passed to a TermReference. */ this.params = null; /** The running count of placeables resolved so far. Used to detect the * Billion Laughs and Quadratic Blowup attacks. */ this.placeables = 0; this.bundle = bundle; this.errors = errors; this.args = args; } reportError(error) { if (!this.errors || !(error instanceof Error)) { throw error; } this.errors.push(error); } memoizeIntlObject(ctor, opts) { let cache = this.bundle._intls.get(ctor); if (!cache) { cache = {}; this.bundle._intls.set(ctor, cache); } let id = JSON.stringify(opts); if (!cache[id]) { cache[id] = new ctor(this.bundle.locales, opts); } return cache[id]; } } ;// CONCATENATED MODULE: ./node_modules/@fluent/bundle/esm/builtins.js /** * @overview * * The FTL resolver ships with a number of functions built-in. * * Each function take two arguments: * - args - an array of positional args * - opts - an object of key-value args * * Arguments to functions are guaranteed to already be instances of * `FluentValue`. Functions must return `FluentValues` as well. */ function values(opts, allowed) { const unwrapped = Object.create(null); for (const [name, opt] of Object.entries(opts)) { if (allowed.includes(name)) { unwrapped[name] = opt.valueOf(); } } return unwrapped; } const NUMBER_ALLOWED = ["unitDisplay", "currencyDisplay", "useGrouping", "minimumIntegerDigits", "minimumFractionDigits", "maximumFractionDigits", "minimumSignificantDigits", "maximumSignificantDigits"]; /** * The implementation of the `NUMBER()` builtin available to translations. * * Translations may call the `NUMBER()` builtin in order to specify formatting * options of a number. For example: * * pi = The value of π is {NUMBER($pi, maximumFractionDigits: 2)}. * * The implementation expects an array of `FluentValues` representing the * positional arguments, and an object of named `FluentValues` representing the * named parameters. * * The following options are recognized: * * unitDisplay * currencyDisplay * useGrouping * minimumIntegerDigits * minimumFractionDigits * maximumFractionDigits * minimumSignificantDigits * maximumSignificantDigits * * Other options are ignored. * * @param args The positional arguments passed to this `NUMBER()`. * @param opts The named argments passed to this `NUMBER()`. */ function NUMBER(args, opts) { let arg = args[0]; if (arg instanceof FluentNone) { return new FluentNone(`NUMBER(${arg.valueOf()})`); } if (arg instanceof FluentNumber) { return new FluentNumber(arg.valueOf(), { ...arg.opts, ...values(opts, NUMBER_ALLOWED) }); } if (arg instanceof FluentDateTime) { return new FluentNumber(arg.valueOf(), { ...values(opts, NUMBER_ALLOWED) }); } throw new TypeError("Invalid argument to NUMBER"); } const DATETIME_ALLOWED = ["dateStyle", "timeStyle", "fractionalSecondDigits", "dayPeriod", "hour12", "weekday", "era", "year", "month", "day", "hour", "minute", "second", "timeZoneName"]; /** * The implementation of the `DATETIME()` builtin available to translations. * * Translations may call the `DATETIME()` builtin in order to specify * formatting options of a number. For example: * * now = It's {DATETIME($today, month: "long")}. * * The implementation expects an array of `FluentValues` representing the * positional arguments, and an object of named `FluentValues` representing the * named parameters. * * The following options are recognized: * * dateStyle * timeStyle * fractionalSecondDigits * dayPeriod * hour12 * weekday * era * year * month * day * hour * minute * second * timeZoneName * * Other options are ignored. * * @param args The positional arguments passed to this `DATETIME()`. * @param opts The named argments passed to this `DATETIME()`. */ function DATETIME(args, opts) { let arg = args[0]; if (arg instanceof FluentNone) { return new FluentNone(`DATETIME(${arg.valueOf()})`); } if (arg instanceof FluentDateTime) { return new FluentDateTime(arg.valueOf(), { ...arg.opts, ...values(opts, DATETIME_ALLOWED) }); } if (arg instanceof FluentNumber) { return new FluentDateTime(arg.valueOf(), { ...values(opts, DATETIME_ALLOWED) }); } throw new TypeError("Invalid argument to DATETIME"); } ;// CONCATENATED MODULE: ./node_modules/@fluent/bundle/esm/memoizer.js const cache = new Map(); function getMemoizerForLocale(locales) { const stringLocale = Array.isArray(locales) ? locales.join(" ") : locales; let memoizer = cache.get(stringLocale); if (memoizer === undefined) { memoizer = new Map(); cache.set(stringLocale, memoizer); } return memoizer; } ;// CONCATENATED MODULE: ./node_modules/@fluent/bundle/esm/bundle.js /** * Message bundles are single-language stores of translation resources. They are * responsible for formatting message values and attributes to strings. */ class FluentBundle { /** * Create an instance of `FluentBundle`. * * The `locales` argument is used to instantiate `Intl` formatters used by * translations. The `options` object can be used to configure the bundle. * * Examples: * * let bundle = new FluentBundle(["en-US", "en"]); * * let bundle = new FluentBundle(locales, {useIsolating: false}); * * let bundle = new FluentBundle(locales, { * useIsolating: true, * functions: { * NODE_ENV: () => process.env.NODE_ENV * } * }); * * Available options: * * - `functions` - an object of additional functions available to * translations as builtins. * * - `useIsolating` - boolean specifying whether to use Unicode isolation * marks (FSI, PDI) for bidi interpolations. Default: `true`. * * - `transform` - a function used to transform string parts of patterns. */ constructor(locales, { functions, useIsolating = true, transform = v => v } = {}) { this._terms = new Map(); this._messages = new Map(); this.locales = Array.isArray(locales) ? locales : [locales]; this._functions = { NUMBER: NUMBER, DATETIME: DATETIME, ...functions }; this._useIsolating = useIsolating; this._transform = transform; this._intls = getMemoizerForLocale(locales); } /** * Check if a message is present in the bundle. * * @param id - The identifier of the message to check. */ hasMessage(id) { return this._messages.has(id); } /** * Return a raw unformatted message object from the bundle. * * Raw messages are `{value, attributes}` shapes containing translation units * called `Patterns`. `Patterns` are implementation-specific; they should be * treated as black boxes and formatted with `FluentBundle.formatPattern`. * * @param id - The identifier of the message to check. */ getMessage(id) { return this._messages.get(id); } /** * Add a translation resource to the bundle. * * The translation resource must be an instance of `FluentResource`. * * let res = new FluentResource("foo = Foo"); * bundle.addResource(res); * bundle.getMessage("foo"); * // → {value: .., attributes: {..}} * * Available options: * * - `allowOverrides` - boolean specifying whether it's allowed to override * an existing message or term with a new value. Default: `false`. * * @param res - FluentResource object. * @param options */ addResource(res, { allowOverrides = false } = {}) { const errors = []; for (let i = 0; i < res.body.length; i++) { let entry = res.body[i]; if (entry.id.startsWith("-")) { // Identifiers starting with a dash (-) define terms. Terms are private // and cannot be retrieved from FluentBundle. if (allowOverrides === false && this._terms.has(entry.id)) { errors.push(new Error(`Attempt to override an existing term: "${entry.id}"`)); continue; } this._terms.set(entry.id, entry); } else { if (allowOverrides === false && this._messages.has(entry.id)) { errors.push(new Error(`Attempt to override an existing message: "${entry.id}"`)); continue; } this._messages.set(entry.id, entry); } } return errors; } /** * Format a `Pattern` to a string. * * Format a raw `Pattern` into a string. `args` will be used to resolve * references to variables passed as arguments to the translation. * * In case of errors `formatPattern` will try to salvage as much of the * translation as possible and will still return a string. For performance * reasons, the encountered errors are not returned but instead are appended * to the `errors` array passed as the third argument. * * let errors = []; * bundle.addResource( * new FluentResource("hello = Hello, {$name}!")); * * let hello = bundle.getMessage("hello"); * if (hello.value) { * bundle.formatPattern(hello.value, {name: "Jane"}, errors); * // Returns "Hello, Jane!" and `errors` is empty. * * bundle.formatPattern(hello.value, undefined, errors); * // Returns "Hello, {$name}!" and `errors` is now: * // [] * } * * If `errors` is omitted, the first encountered error will be thrown. */ formatPattern(pattern, args = null, errors = null) { // Resolve a simple pattern without creating a scope. No error handling is // required; by definition simple patterns don't have placeables. if (typeof pattern === "string") { return this._transform(pattern); } // Resolve a complex pattern. let scope = new Scope(this, errors, args); try { let value = resolveComplexPattern(scope, pattern); return value.toString(scope); } catch (err) { if (scope.errors && err instanceof Error) { scope.errors.push(err); return new FluentNone().toString(scope); } throw err; } } } ;// CONCATENATED MODULE: ./node_modules/@fluent/bundle/esm/resource.js // This regex is used to iterate through the beginnings of messages and terms. // With the /m flag, the ^ matches at the beginning of every line. const RE_MESSAGE_START = /^(-?[a-zA-Z][\w-]*) *= */gm; // Both Attributes and Variants are parsed in while loops. These regexes are // used to break out of them. const RE_ATTRIBUTE_START = /\.([a-zA-Z][\w-]*) *= */y; const RE_VARIANT_START = /\*?\[/y; const RE_NUMBER_LITERAL = /(-?[0-9]+(?:\.([0-9]+))?)/y; const RE_IDENTIFIER = /([a-zA-Z][\w-]*)/y; const RE_REFERENCE = /([$-])?([a-zA-Z][\w-]*)(?:\.([a-zA-Z][\w-]*))?/y; const RE_FUNCTION_NAME = /^[A-Z][A-Z0-9_-]*$/; // A "run" is a sequence of text or string literal characters which don't // require any special handling. For TextElements such special characters are: { // (starts a placeable), and line breaks which require additional logic to check // if the next line is indented. For StringLiterals they are: \ (starts an // escape sequence), " (ends the literal), and line breaks which are not allowed // in StringLiterals. Note that string runs may be empty; text runs may not. const RE_TEXT_RUN = /([^{}\n\r]+)/y; const RE_STRING_RUN = /([^\\"\n\r]*)/y; // Escape sequences. const RE_STRING_ESCAPE = /\\([\\"])/y; const RE_UNICODE_ESCAPE = /\\u([a-fA-F0-9]{4})|\\U([a-fA-F0-9]{6})/y; // Used for trimming TextElements and indents. const RE_LEADING_NEWLINES = /^\n+/; const RE_TRAILING_SPACES = / +$/; // Used in makeIndent to strip spaces from blank lines and normalize CRLF to LF. const RE_BLANK_LINES = / *\r?\n/g; // Used in makeIndent to measure the indentation. const RE_INDENT = /( *)$/; // Common tokens. const TOKEN_BRACE_OPEN = /{\s*/y; const TOKEN_BRACE_CLOSE = /\s*}/y; const TOKEN_BRACKET_OPEN = /\[\s*/y; const TOKEN_BRACKET_CLOSE = /\s*] */y; const TOKEN_PAREN_OPEN = /\s*\(\s*/y; const TOKEN_ARROW = /\s*->\s*/y; const TOKEN_COLON = /\s*:\s*/y; // Note the optional comma. As a deviation from the Fluent EBNF, the parser // doesn't enforce commas between call arguments. const TOKEN_COMMA = /\s*,?\s*/y; const TOKEN_BLANK = /\s+/y; /** * Fluent Resource is a structure storing parsed localization entries. */ class FluentResource { constructor(source) { this.body = []; RE_MESSAGE_START.lastIndex = 0; let cursor = 0; // Iterate over the beginnings of messages and terms to efficiently skip // comments and recover from errors. while (true) { let next = RE_MESSAGE_START.exec(source); if (next === null) { break; } cursor = RE_MESSAGE_START.lastIndex; try { this.body.push(parseMessage(next[1])); } catch (err) { if (err instanceof SyntaxError) { // Don't report any Fluent syntax errors. Skip directly to the // beginning of the next message or term. continue; } throw err; } } // The parser implementation is inlined below for performance reasons, // as well as for convenience of accessing `source` and `cursor`. // The parser focuses on minimizing the number of false negatives at the // expense of increasing the risk of false positives. In other words, it // aims at parsing valid Fluent messages with a success rate of 100%, but it // may also parse a few invalid messages which the reference parser would // reject. The parser doesn't perform any validation and may produce entries // which wouldn't make sense in the real world. For best results users are // advised to validate translations with the fluent-syntax parser // pre-runtime. // The parser makes an extensive use of sticky regexes which can be anchored // to any offset of the source string without slicing it. Errors are thrown // to bail out of parsing of ill-formed messages. function test(re) { re.lastIndex = cursor; return re.test(source); } // Advance the cursor by the char if it matches. May be used as a predicate // (was the match found?) or, if errorClass is passed, as an assertion. function consumeChar(char, errorClass) { if (source[cursor] === char) { cursor++; return true; } if (errorClass) { throw new errorClass(`Expected ${char}`); } return false; } // Advance the cursor by the token if it matches. May be used as a predicate // (was the match found?) or, if errorClass is passed, as an assertion. function consumeToken(re, errorClass) { if (test(re)) { cursor = re.lastIndex; return true; } if (errorClass) { throw new errorClass(`Expected ${re.toString()}`); } return false; } // Execute a regex, advance the cursor, and return all capture groups. function match(re) { re.lastIndex = cursor; let result = re.exec(source); if (result === null) { throw new SyntaxError(`Expected ${re.toString()}`); } cursor = re.lastIndex; return result; } // Execute a regex, advance the cursor, and return the capture group. function match1(re) { return match(re)[1]; } function parseMessage(id) { let value = parsePattern(); let attributes = parseAttributes(); if (value === null && Object.keys(attributes).length === 0) { throw new SyntaxError("Expected message value or attributes"); } return { id, value, attributes }; } function parseAttributes() { let attrs = Object.create(null); while (test(RE_ATTRIBUTE_START)) { let name = match1(RE_ATTRIBUTE_START); let value = parsePattern(); if (value === null) { throw new SyntaxError("Expected attribute value"); } attrs[name] = value; } return attrs; } function parsePattern() { let first; // First try to parse any simple text on the same line as the id. if (test(RE_TEXT_RUN)) { first = match1(RE_TEXT_RUN); } // If there's a placeable on the first line, parse a complex pattern. if (source[cursor] === "{" || source[cursor] === "}") { // Re-use the text parsed above, if possible. return parsePatternElements(first ? [first] : [], Infinity); } // RE_TEXT_VALUE stops at newlines. Only continue parsing the pattern if // what comes after the newline is indented. let indent = parseIndent(); if (indent) { if (first) { // If there's text on the first line, the blank block is part of the // translation content in its entirety. return parsePatternElements([first, indent], indent.length); } // Otherwise, we're dealing with a block pattern, i.e. a pattern which // starts on a new line. Discrad the leading newlines but keep the // inline indent; it will be used by the dedentation logic. indent.value = trim(indent.value, RE_LEADING_NEWLINES); return parsePatternElements([indent], indent.length); } if (first) { // It was just a simple inline text after all. return trim(first, RE_TRAILING_SPACES); } return null; } // Parse a complex pattern as an array of elements. function parsePatternElements(elements = [], commonIndent) { while (true) { if (test(RE_TEXT_RUN)) { elements.push(match1(RE_TEXT_RUN)); continue; } if (source[cursor] === "{") { elements.push(parsePlaceable()); continue; } if (source[cursor] === "}") { throw new SyntaxError("Unbalanced closing brace"); } let indent = parseIndent(); if (indent) { elements.push(indent); commonIndent = Math.min(commonIndent, indent.length); continue; } break; } let lastIndex = elements.length - 1; let lastElement = elements[lastIndex]; // Trim the trailing spaces in the last element if it's a TextElement. if (typeof lastElement === "string") { elements[lastIndex] = trim(lastElement, RE_TRAILING_SPACES); } let baked = []; for (let element of elements) { if (element instanceof Indent) { // Dedent indented lines by the maximum common indent. element = element.value.slice(0, element.value.length - commonIndent); } if (element) { baked.push(element); } } return baked; } function parsePlaceable() { consumeToken(TOKEN_BRACE_OPEN, SyntaxError); let selector = parseInlineExpression(); if (consumeToken(TOKEN_BRACE_CLOSE)) { return selector; } if (consumeToken(TOKEN_ARROW)) { let variants = parseVariants(); consumeToken(TOKEN_BRACE_CLOSE, SyntaxError); return { type: "select", selector, ...variants }; } throw new SyntaxError("Unclosed placeable"); } function parseInlineExpression() { if (source[cursor] === "{") { // It's a nested placeable. return parsePlaceable(); } if (test(RE_REFERENCE)) { let [, sigil, name, attr = null] = match(RE_REFERENCE); if (sigil === "$") { return { type: "var", name }; } if (consumeToken(TOKEN_PAREN_OPEN)) { let args = parseArguments(); if (sigil === "-") { // A parameterized term: -term(...). return { type: "term", name, attr, args }; } if (RE_FUNCTION_NAME.test(name)) { return { type: "func", name, args }; } throw new SyntaxError("Function names must be all upper-case"); } if (sigil === "-") { // A non-parameterized term: -term. return { type: "term", name, attr, args: [] }; } return { type: "mesg", name, attr }; } return parseLiteral(); } function parseArguments() { let args = []; while (true) { switch (source[cursor]) { case ")": // End of the argument list. cursor++; return args; case undefined: // EOF throw new SyntaxError("Unclosed argument list"); } args.push(parseArgument()); // Commas between arguments are treated as whitespace. consumeToken(TOKEN_COMMA); } } function parseArgument() { let expr = parseInlineExpression(); if (expr.type !== "mesg") { return expr; } if (consumeToken(TOKEN_COLON)) { // The reference is the beginning of a named argument. return { type: "narg", name: expr.name, value: parseLiteral() }; } // It's a regular message reference. return expr; } function parseVariants() { let variants = []; let count = 0; let star; while (test(RE_VARIANT_START)) { if (consumeChar("*")) { star = count; } let key = parseVariantKey(); let value = parsePattern(); if (value === null) { throw new SyntaxError("Expected variant value"); } variants[count++] = { key, value }; } if (count === 0) { return null; } if (star === undefined) { throw new SyntaxError("Expected default variant"); } return { variants, star }; } function parseVariantKey() { consumeToken(TOKEN_BRACKET_OPEN, SyntaxError); let key; if (test(RE_NUMBER_LITERAL)) { key = parseNumberLiteral(); } else { key = { type: "str", value: match1(RE_IDENTIFIER) }; } consumeToken(TOKEN_BRACKET_CLOSE, SyntaxError); return key; } function parseLiteral() { if (test(RE_NUMBER_LITERAL)) { return parseNumberLiteral(); } if (source[cursor] === '"') { return parseStringLiteral(); } throw new SyntaxError("Invalid expression"); } function parseNumberLiteral() { let [, value, fraction = ""] = match(RE_NUMBER_LITERAL); let precision = fraction.length; return { type: "num", value: parseFloat(value), precision }; } function parseStringLiteral() { consumeChar('"', SyntaxError); let value = ""; while (true) { value += match1(RE_STRING_RUN); if (source[cursor] === "\\") { value += parseEscapeSequence(); continue; } if (consumeChar('"')) { return { type: "str", value }; } // We've reached an EOL of EOF. throw new SyntaxError("Unclosed string literal"); } } // Unescape known escape sequences. function parseEscapeSequence() { if (test(RE_STRING_ESCAPE)) { return match1(RE_STRING_ESCAPE); } if (test(RE_UNICODE_ESCAPE)) { let [, codepoint4, codepoint6] = match(RE_UNICODE_ESCAPE); let codepoint = parseInt(codepoint4 || codepoint6, 16); return codepoint <= 0xd7ff || 0xe000 <= codepoint // It's a Unicode scalar value. ? String.fromCodePoint(codepoint) // Lonely surrogates can cause trouble when the parsing result is // saved using UTF-8. Use U+FFFD REPLACEMENT CHARACTER instead. : "�"; } throw new SyntaxError("Unknown escape sequence"); } // Parse blank space. Return it if it looks like indent before a pattern // line. Skip it othwerwise. function parseIndent() { let start = cursor; consumeToken(TOKEN_BLANK); // Check the first non-blank character after the indent. switch (source[cursor]) { case ".": case "[": case "*": case "}": case undefined: // EOF // A special character. End the Pattern. return false; case "{": // Placeables don't require indentation (in EBNF: block-placeable). // Continue the Pattern. return makeIndent(source.slice(start, cursor)); } // If the first character on the line is not one of the special characters // listed above, it's a regular text character. Check if there's at least // one space of indent before it. if (source[cursor - 1] === " ") { // It's an indented text character (in EBNF: indented-char). Continue // the Pattern. return makeIndent(source.slice(start, cursor)); } // A not-indented text character is likely the identifier of the next // message. End the Pattern. return false; } // Trim blanks in text according to the given regex. function trim(text, re) { return text.replace(re, ""); } // Normalize a blank block and extract the indent details. function makeIndent(blank) { let value = blank.replace(RE_BLANK_LINES, "\n"); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion let length = RE_INDENT.exec(blank)[1].length; return new Indent(value, length); } } } class Indent { constructor(value, length) { this.value = value; this.length = length; } } ;// CONCATENATED MODULE: ./node_modules/@fluent/bundle/esm/index.js /** * @module fluent * @overview * * `fluent` is a JavaScript implementation of Project Fluent, a localization * framework designed to unleash the expressive power of the natural language. * */ ;// CONCATENATED MODULE: ./content-src/asrouter/rich-text-strings.js /* 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/. */ /** * Properties that allow rich text MUST be added to this list. * key: the localization_id that should be used * value: a property or array of properties on the message.content object */ const RICH_TEXT_CONFIG = { text: ["text", "scene1_text"], success_text: "success_text", error_text: "error_text", scene2_text: "scene2_text", amo_html: "amo_html", privacy_html: "scene2_privacy_html", disclaimer_html: "scene2_disclaimer_html" }; const RICH_TEXT_KEYS = Object.keys(RICH_TEXT_CONFIG); /** * Generates an array of messages suitable for fluent's localization provider * including all needed strings for rich text. * @param {object} content A .content object from an ASR message (i.e. message.content) * @returns {FluentBundle[]} A array containing the fluent message context */ function generateBundles(content) { const bundle = new FluentBundle("en-US"); RICH_TEXT_KEYS.forEach(key => { const attrs = RICH_TEXT_CONFIG[key]; const attrsToTry = Array.isArray(attrs) ? [...attrs] : [attrs]; let string = ""; while (!string && attrsToTry.length) { const attr = attrsToTry.pop(); string = content[attr]; } bundle.addResource(new FluentResource(`${key} = ${string}`)); }); return [bundle]; } ;// CONCATENATED MODULE: ./content-src/asrouter/components/ImpressionsWrapper/ImpressionsWrapper.jsx /* 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 VISIBLE = "visible"; const VISIBILITY_CHANGE_EVENT = "visibilitychange"; /** * Component wrapper used to send telemetry pings on every impression. */ class ImpressionsWrapper extends (external_React_default()).PureComponent { // This sends an event when a user sees a set of new content. If content // changes while the page is hidden (i.e. preloaded or on a hidden tab), // only send the event if the page becomes visible again. sendImpressionOrAddListener() { if (this.props.document.visibilityState === VISIBLE) { this.props.sendImpression({ id: this.props.id }); } else { // We should only ever send the latest impression stats ping, so remove any // older listeners. if (this._onVisibilityChange) { this.props.document.removeEventListener(VISIBILITY_CHANGE_EVENT, this._onVisibilityChange); } // When the page becomes visible, send the impression stats ping if the section isn't collapsed. this._onVisibilityChange = () => { if (this.props.document.visibilityState === VISIBLE) { this.props.sendImpression({ id: this.props.id }); this.props.document.removeEventListener(VISIBILITY_CHANGE_EVENT, this._onVisibilityChange); } }; this.props.document.addEventListener(VISIBILITY_CHANGE_EVENT, this._onVisibilityChange); } } componentWillUnmount() { if (this._onVisibilityChange) { this.props.document.removeEventListener(VISIBILITY_CHANGE_EVENT, this._onVisibilityChange); } } componentDidMount() { if (this.props.sendOnMount) { this.sendImpressionOrAddListener(); } } componentDidUpdate(prevProps) { if (this.props.shouldSendImpressionOnUpdate(this.props, prevProps)) { this.sendImpressionOrAddListener(); } } render() { return this.props.children; } } ImpressionsWrapper.defaultProps = { document: __webpack_require__.g.document, sendOnMount: true }; ;// CONCATENATED MODULE: ./node_modules/@fluent/sequence/esm/map_sync.js /** * Synchronously map an identifier or an array of identifiers to the best * `FluentBundle` instance(s). * * @param bundles - An iterable of bundles to sift through. * @param ids - An id or ids to map. */ function mapBundleSync(bundles, ids) { if (!Array.isArray(ids)) { return getBundleForId(bundles, ids); } return ids.map(id => getBundleForId(bundles, id)); } /* * Find the best `FluentBundle` with the translation for `id`. */ function getBundleForId(bundles, id) { for (const bundle of bundles) { if (bundle.hasMessage(id)) { return bundle; } } return null; } ;// CONCATENATED MODULE: ./node_modules/@fluent/sequence/esm/map_async.js /** * Asynchronously map an identifier or an array of identifiers to the best * `FluentBundle` instance(s). * * @param bundles - An iterable of bundles to sift through. * @param ids - An id or ids to map. */ async function mapBundleAsync(bundles, ids) { if (!Array.isArray(ids)) { for await (const bundle of bundles) { if (bundle.hasMessage(ids)) { return bundle; } } return null; } const foundBundles = new Array(ids.length).fill(null); let remainingCount = ids.length; for await (const bundle of bundles) { for (const [index, id] of ids.entries()) { if (!foundBundles[index] && bundle.hasMessage(id)) { foundBundles[index] = bundle; remainingCount--; } // Return early when all ids have been mapped to bundles. if (remainingCount === 0) { return foundBundles; } } } return foundBundles; } ;// CONCATENATED MODULE: ./node_modules/@fluent/sequence/esm/index.js /** * @module fluent-sequence * @overview Manage ordered sequences of FluentBundles. */ ;// CONCATENATED MODULE: ./node_modules/@fluent/react/node_modules/cached-iterable/src/cached_iterable.mjs /* * Base CachedIterable class. */ class CachedIterable extends Array { /** * Create a `CachedIterable` instance from an iterable or, if another * instance of `CachedIterable` is passed, return it without any * modifications. * * @param {Iterable} iterable * @returns {CachedIterable} */ static from(iterable) { if (iterable instanceof this) { return iterable; } return new this(iterable); } } ;// CONCATENATED MODULE: ./node_modules/@fluent/react/node_modules/cached-iterable/src/cached_sync_iterable.mjs /* * CachedSyncIterable caches the elements yielded by an iterable. * * It can be used to iterate over an iterable many times without depleting the * iterable. */ class CachedSyncIterable extends CachedIterable { /** * Create an `CachedSyncIterable` instance. * * @param {Iterable} iterable * @returns {CachedSyncIterable} */ constructor(iterable) { super(); if (Symbol.iterator in Object(iterable)) { this.iterator = iterable[Symbol.iterator](); } else { throw new TypeError("Argument must implement the iteration protocol."); } } [Symbol.iterator]() { const cached = this; let cur = 0; return { next() { if (cached.length <= cur) { cached.push(cached.iterator.next()); } return cached[cur++]; } }; } /** * This method allows user to consume the next element from the iterator * into the cache. * * @param {number} count - number of elements to consume */ touchNext(count = 1) { let idx = 0; while (idx++ < count) { const last = this[this.length - 1]; if (last && last.done) { break; } this.push(this.iterator.next()); } // Return the last cached {value, done} object to allow the calling // code to decide if it needs to call touchNext again. return this[this.length - 1]; } } ;// CONCATENATED MODULE: ./node_modules/@fluent/react/node_modules/cached-iterable/src/cached_async_iterable.mjs /* * CachedAsyncIterable caches the elements yielded by an async iterable. * * It can be used to iterate over an iterable many times without depleting the * iterable. */ class CachedAsyncIterable extends CachedIterable { /** * Create an `CachedAsyncIterable` instance. * * @param {Iterable} iterable * @returns {CachedAsyncIterable} */ constructor(iterable) { super(); if (Symbol.asyncIterator in Object(iterable)) { this.iterator = iterable[Symbol.asyncIterator](); } else if (Symbol.iterator in Object(iterable)) { this.iterator = iterable[Symbol.iterator](); } else { throw new TypeError("Argument must implement the iteration protocol."); } } /** * Asynchronous iterator caching the yielded elements. * * Elements yielded by the original iterable will be cached and available * synchronously. Returns an async generator object implementing the * iterator protocol over the elements of the original (async or sync) * iterable. */ [Symbol.asyncIterator]() { const cached = this; let cur = 0; return { async next() { if (cached.length <= cur) { cached.push(cached.iterator.next()); } return cached[cur++]; } }; } /** * This method allows user to consume the next element from the iterator * into the cache. * * @param {number} count - number of elements to consume */ async touchNext(count = 1) { let idx = 0; while (idx++ < count) { const last = this[this.length - 1]; if (last && (await last).done) { break; } this.push(this.iterator.next()); } // Return the last cached {value, done} object to allow the calling // code to decide if it needs to call touchNext again. return this[this.length - 1]; } } ;// CONCATENATED MODULE: ./node_modules/@fluent/react/node_modules/cached-iterable/src/index.mjs ;// CONCATENATED MODULE: ./node_modules/@fluent/react/esm/markup.js let cachedParseMarkup; /** * We use a function creator to make the reference to `document` lazy. At the * same time, it's eager enough to throw in `` as soon as * it's first mounted which reduces the risk of this error making it to the * runtime without developers noticing it in development. */ function createParseMarkup() { if (typeof document === "undefined") { // We can't use