/*! THIS FILE IS AUTO-GENERATED: webpack.system-addon.config.js */ var NewtabRenderUtils; /******/ (() => { // webpackBootstrap /******/ "use strict"; /******/ var __webpack_modules__ = ([ /* 0 */, /* 1 */ /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "MAIN_MESSAGE_TYPE": () => (/* binding */ MAIN_MESSAGE_TYPE), /* harmony export */ "CONTENT_MESSAGE_TYPE": () => (/* binding */ CONTENT_MESSAGE_TYPE), /* harmony export */ "PRELOAD_MESSAGE_TYPE": () => (/* binding */ PRELOAD_MESSAGE_TYPE), /* harmony export */ "UI_CODE": () => (/* binding */ UI_CODE), /* harmony export */ "BACKGROUND_PROCESS": () => (/* binding */ BACKGROUND_PROCESS), /* harmony export */ "globalImportContext": () => (/* binding */ globalImportContext), /* harmony export */ "actionTypes": () => (/* binding */ actionTypes), /* harmony export */ "actionCreators": () => (/* binding */ actionCreators), /* harmony export */ "actionUtils": () => (/* binding */ actionUtils) /* harmony export */ }); /* 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_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_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_IMPRESSION_STATS", "TOP_SITES_INSERT", "TOP_SITES_OPEN_SEARCH_SHORTCUTS_MODAL", "TOP_SITES_PIN", "TOP_SITES_PREFS_UPDATED", "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, }; /***/ }), /* 2 */ /***/ ((module, __unused_webpack_exports, __webpack_require__) => { /* 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 { actionTypes: at } = __webpack_require__(1); const { Dedupe } = __webpack_require__(3); const TOP_SITES_DEFAULT_ROWS = 1; const TOP_SITES_MAX_SITES_PER_ROW = 8; const PREF_COLLECTION_DISMISSIBLE = "discoverystream.isCollectionDismissible"; const dedupe = new Dedupe(site => site && site.url); const INITIAL_STATE = { App: { // Have we received real data from the app yet? initialized: false, locale: "" }, ASRouter: { initialized: false }, Snippets: { initialized: false }, TopSites: { // Have we received real data from history yet? initialized: false, // The history (and possibly default) links rows: [], // Used in content only to dispatch action to TopSiteForm. editForm: null, // Used in content only to open the SearchShortcutsForm modal. showSearchShortcutsForm: false, // The list of available search shortcuts. searchShortcuts: [] }, Prefs: { initialized: false, values: { featureConfig: {} } }, Dialog: { visible: false, data: {} }, Sections: [], Pocket: { isUserLoggedIn: null, pocketCta: {}, waitingForSpoc: true }, // This is the new pocket configurable layout state. DiscoveryStream: { // This is a JSON-parsed copy of the discoverystream.config pref value. config: { enabled: false, layout_endpoint: "" }, layout: [], lastUpdated: null, isPrivacyInfoModalVisible: false, isCollectionDismissible: false, feeds: { data: {// "https://foo.com/feed1": {lastUpdated: 123, data: []} }, loaded: false }, spocs: { spocs_endpoint: "", lastUpdated: null, data: {// "spocs": {title: "", context: "", items: []}, // "placement1": {title: "", context: "", items: []}, }, loaded: false, frequency_caps: [], blocked: [], placements: [] }, experimentData: { utmSource: "pocket-newtab", utmCampaign: undefined, utmContent: undefined }, recentSavesData: [], isUserLoggedIn: false, recentSavesEnabled: false }, Personalization: { lastUpdated: null, initialized: false }, Search: { // When search hand-off is enabled, we render a big button that is styled to // look like a search textbox. If the button is clicked, we style // the button as if it was a focused search box and show a fake cursor but // really focus the awesomebar without the focus styles ("hidden focus"). fakeFocus: false, // Hide the search box after handing off to AwesomeBar and user starts typing. hide: false } }; function App(prevState = INITIAL_STATE.App, action) { switch (action.type) { case at.INIT: return Object.assign({}, prevState, action.data || {}, { initialized: true }); default: return prevState; } } function ASRouter(prevState = INITIAL_STATE.ASRouter, action) { switch (action.type) { case at.AS_ROUTER_INITIALIZED: return { ...action.data, initialized: true }; default: return prevState; } } /** * insertPinned - Inserts pinned links in their specified slots * * @param {array} a list of links * @param {array} a list of pinned links * @return {array} resulting list of links with pinned links inserted */ function insertPinned(links, pinned) { // Remove any pinned links const pinnedUrls = pinned.map(link => link && link.url); let newLinks = links.filter(link => link ? !pinnedUrls.includes(link.url) : false); newLinks = newLinks.map(link => { if (link && link.isPinned) { delete link.isPinned; delete link.pinIndex; } return link; }); // Then insert them in their specified location pinned.forEach((val, index) => { if (!val) { return; } let link = Object.assign({}, val, { isPinned: true, pinIndex: index }); if (index > newLinks.length) { newLinks[index] = link; } else { newLinks.splice(index, 0, link); } }); return newLinks; } function TopSites(prevState = INITIAL_STATE.TopSites, action) { let hasMatch; let newRows; switch (action.type) { case at.TOP_SITES_UPDATED: if (!action.data || !action.data.links) { return prevState; } return Object.assign({}, prevState, { initialized: true, rows: action.data.links }, action.data.pref ? { pref: action.data.pref } : {}); case at.TOP_SITES_PREFS_UPDATED: return Object.assign({}, prevState, { pref: action.data.pref }); case at.TOP_SITES_EDIT: return Object.assign({}, prevState, { editForm: { index: action.data.index, previewResponse: null } }); case at.TOP_SITES_CANCEL_EDIT: return Object.assign({}, prevState, { editForm: null }); case at.TOP_SITES_OPEN_SEARCH_SHORTCUTS_MODAL: return Object.assign({}, prevState, { showSearchShortcutsForm: true }); case at.TOP_SITES_CLOSE_SEARCH_SHORTCUTS_MODAL: return Object.assign({}, prevState, { showSearchShortcutsForm: false }); case at.PREVIEW_RESPONSE: if (!prevState.editForm || action.data.url !== prevState.editForm.previewUrl) { return prevState; } return Object.assign({}, prevState, { editForm: { index: prevState.editForm.index, previewResponse: action.data.preview, previewUrl: action.data.url } }); case at.PREVIEW_REQUEST: if (!prevState.editForm) { return prevState; } return Object.assign({}, prevState, { editForm: { index: prevState.editForm.index, previewResponse: null, previewUrl: action.data.url } }); case at.PREVIEW_REQUEST_CANCEL: if (!prevState.editForm) { return prevState; } return Object.assign({}, prevState, { editForm: { index: prevState.editForm.index, previewResponse: null } }); case at.SCREENSHOT_UPDATED: newRows = prevState.rows.map(row => { if (row && row.url === action.data.url) { hasMatch = true; return Object.assign({}, row, { screenshot: action.data.screenshot }); } return row; }); return hasMatch ? Object.assign({}, prevState, { rows: newRows }) : prevState; case at.PLACES_BOOKMARK_ADDED: if (!action.data) { return prevState; } newRows = prevState.rows.map(site => { if (site && site.url === action.data.url) { const { bookmarkGuid, bookmarkTitle, dateAdded } = action.data; return Object.assign({}, site, { bookmarkGuid, bookmarkTitle, bookmarkDateCreated: dateAdded }); } return site; }); return Object.assign({}, prevState, { rows: newRows }); case at.PLACES_BOOKMARKS_REMOVED: if (!action.data) { return prevState; } newRows = prevState.rows.map(site => { if (site && action.data.urls.includes(site.url)) { const newSite = Object.assign({}, site); delete newSite.bookmarkGuid; delete newSite.bookmarkTitle; delete newSite.bookmarkDateCreated; return newSite; } return site; }); return Object.assign({}, prevState, { rows: newRows }); case at.PLACES_LINKS_DELETED: if (!action.data) { return prevState; } newRows = prevState.rows.filter(site => !action.data.urls.includes(site.url)); return Object.assign({}, prevState, { rows: newRows }); case at.UPDATE_SEARCH_SHORTCUTS: return { ...prevState, searchShortcuts: action.data.searchShortcuts }; case at.SNIPPETS_PREVIEW_MODE: return { ...prevState, rows: [] }; default: return prevState; } } function Dialog(prevState = INITIAL_STATE.Dialog, action) { switch (action.type) { case at.DIALOG_OPEN: return Object.assign({}, prevState, { visible: true, data: action.data }); case at.DIALOG_CANCEL: return Object.assign({}, prevState, { visible: false }); case at.DELETE_HISTORY_URL: return Object.assign({}, INITIAL_STATE.Dialog); default: return prevState; } } function Prefs(prevState = INITIAL_STATE.Prefs, action) { let newValues; switch (action.type) { case at.PREFS_INITIAL_VALUES: return Object.assign({}, prevState, { initialized: true, values: action.data }); case at.PREF_CHANGED: newValues = Object.assign({}, prevState.values); newValues[action.data.name] = action.data.value; return Object.assign({}, prevState, { values: newValues }); default: return prevState; } } function Sections(prevState = INITIAL_STATE.Sections, action) { let hasMatch; let newState; switch (action.type) { case at.SECTION_DEREGISTER: return prevState.filter(section => section.id !== action.data); case at.SECTION_REGISTER: // If section exists in prevState, update it newState = prevState.map(section => { if (section && section.id === action.data.id) { hasMatch = true; return Object.assign({}, section, action.data); } return section; }); // Otherwise, append it if (!hasMatch) { const initialized = !!(action.data.rows && !!action.data.rows.length); const section = Object.assign({ title: "", rows: [], enabled: false }, action.data, { initialized }); newState.push(section); } return newState; case at.SECTION_UPDATE: newState = prevState.map(section => { if (section && section.id === action.data.id) { // If the action is updating rows, we should consider initialized to be true. // This can be overridden if initialized is defined in the action.data const initialized = action.data.rows ? { initialized: true } : {}; // Make sure pinned cards stay at their current position when rows are updated. // Disabling a section (SECTION_UPDATE with empty rows) does not retain pinned cards. if (action.data.rows && !!action.data.rows.length && section.rows.find(card => card.pinned)) { const rows = Array.from(action.data.rows); section.rows.forEach((card, index) => { if (card.pinned) { // Only add it if it's not already there. if (rows[index].guid !== card.guid) { rows.splice(index, 0, card); } } }); return Object.assign({}, section, initialized, Object.assign({}, action.data, { rows })); } return Object.assign({}, section, initialized, action.data); } return section; }); if (!action.data.dedupeConfigurations) { return newState; } action.data.dedupeConfigurations.forEach(dedupeConf => { newState = newState.map(section => { if (section.id === dedupeConf.id) { const dedupedRows = dedupeConf.dedupeFrom.reduce((rows, dedupeSectionId) => { const dedupeSection = newState.find(s => s.id === dedupeSectionId); const [, newRows] = dedupe.group(dedupeSection.rows, rows); return newRows; }, section.rows); return Object.assign({}, section, { rows: dedupedRows }); } return section; }); }); return newState; case at.SECTION_UPDATE_CARD: return prevState.map(section => { if (section && section.id === action.data.id && section.rows) { const newRows = section.rows.map(card => { if (card.url === action.data.url) { return Object.assign({}, card, action.data.options); } return card; }); return Object.assign({}, section, { rows: newRows }); } return section; }); case at.PLACES_BOOKMARK_ADDED: if (!action.data) { return prevState; } return prevState.map(section => Object.assign({}, section, { rows: section.rows.map(item => { // find the item within the rows that is attempted to be bookmarked if (item.url === action.data.url) { const { bookmarkGuid, bookmarkTitle, dateAdded } = action.data; return Object.assign({}, item, { bookmarkGuid, bookmarkTitle, bookmarkDateCreated: dateAdded, type: "bookmark" }); } return item; }) })); case at.PLACES_SAVED_TO_POCKET: if (!action.data) { return prevState; } return prevState.map(section => Object.assign({}, section, { rows: section.rows.map(item => { if (item.url === action.data.url) { return Object.assign({}, item, { open_url: action.data.open_url, pocket_id: action.data.pocket_id, title: action.data.title, type: "pocket" }); } return item; }) })); case at.PLACES_BOOKMARKS_REMOVED: if (!action.data) { return prevState; } return prevState.map(section => Object.assign({}, section, { rows: section.rows.map(item => { // find the bookmark within the rows that is attempted to be removed if (action.data.urls.includes(item.url)) { const newSite = Object.assign({}, item); delete newSite.bookmarkGuid; delete newSite.bookmarkTitle; delete newSite.bookmarkDateCreated; if (!newSite.type || newSite.type === "bookmark") { newSite.type = "history"; } return newSite; } return item; }) })); case at.PLACES_LINKS_DELETED: if (!action.data) { return prevState; } return prevState.map(section => Object.assign({}, section, { rows: section.rows.filter(site => !action.data.urls.includes(site.url)) })); case at.PLACES_LINK_BLOCKED: if (!action.data) { return prevState; } return prevState.map(section => Object.assign({}, section, { rows: section.rows.filter(site => site.url !== action.data.url) })); case at.DELETE_FROM_POCKET: case at.ARCHIVE_FROM_POCKET: return prevState.map(section => Object.assign({}, section, { rows: section.rows.filter(site => site.pocket_id !== action.data.pocket_id) })); case at.SNIPPETS_PREVIEW_MODE: return prevState.map(section => ({ ...section, rows: [] })); default: return prevState; } } function Snippets(prevState = INITIAL_STATE.Snippets, action) { switch (action.type) { case at.SNIPPETS_DATA: return Object.assign({}, prevState, { initialized: true }, action.data); case at.SNIPPET_BLOCKED: return Object.assign({}, prevState, { blockList: prevState.blockList.concat(action.data) }); case at.SNIPPETS_BLOCKLIST_CLEARED: return Object.assign({}, prevState, { blockList: [] }); case at.SNIPPETS_RESET: return INITIAL_STATE.Snippets; default: return prevState; } } function Pocket(prevState = INITIAL_STATE.Pocket, action) { switch (action.type) { case at.POCKET_WAITING_FOR_SPOC: return { ...prevState, waitingForSpoc: action.data }; case at.POCKET_LOGGED_IN: return { ...prevState, isUserLoggedIn: !!action.data }; case at.POCKET_CTA: return { ...prevState, pocketCta: { ctaButton: action.data.cta_button, ctaText: action.data.cta_text, ctaUrl: action.data.cta_url, useCta: action.data.use_cta } }; default: return prevState; } } function Personalization(prevState = INITIAL_STATE.Personalization, action) { switch (action.type) { case at.DISCOVERY_STREAM_PERSONALIZATION_LAST_UPDATED: return { ...prevState, lastUpdated: action.data.lastUpdated }; case at.DISCOVERY_STREAM_PERSONALIZATION_INIT: return { ...prevState, initialized: true }; default: return prevState; } } // eslint-disable-next-line complexity function DiscoveryStream(prevState = INITIAL_STATE.DiscoveryStream, action) { // Return if action data is empty, or spocs or feeds data is not loaded const isNotReady = () => !action.data || !prevState.spocs.loaded || !prevState.feeds.loaded; const handlePlacements = handleSites => { const { data, placements } = prevState.spocs; const result = {}; const forPlacement = placement => { const placementSpocs = data[placement.name]; if (!placementSpocs || !placementSpocs.items || !placementSpocs.items.length) { return; } result[placement.name] = { ...placementSpocs, items: handleSites(placementSpocs.items) }; }; if (!placements || !placements.length) { [{ name: "spocs" }].forEach(forPlacement); } else { placements.forEach(forPlacement); } return result; }; const nextState = handleSites => ({ ...prevState, spocs: { ...prevState.spocs, data: handlePlacements(handleSites) }, feeds: { ...prevState.feeds, data: Object.keys(prevState.feeds.data).reduce((accumulator, feed_url) => { accumulator[feed_url] = { data: { ...prevState.feeds.data[feed_url].data, recommendations: handleSites(prevState.feeds.data[feed_url].data.recommendations) } }; return accumulator; }, {}) } }); switch (action.type) { case at.DISCOVERY_STREAM_CONFIG_CHANGE: // Fall through to a separate action is so it doesn't trigger a listener update on init case at.DISCOVERY_STREAM_CONFIG_SETUP: return { ...prevState, config: action.data || {} }; case at.DISCOVERY_STREAM_EXPERIMENT_DATA: return { ...prevState, experimentData: action.data || {} }; case at.DISCOVERY_STREAM_LAYOUT_UPDATE: return { ...prevState, lastUpdated: action.data.lastUpdated || null, layout: action.data.layout || [] }; case at.DISCOVERY_STREAM_COLLECTION_DISMISSIBLE_TOGGLE: return { ...prevState, isCollectionDismissible: action.data.value }; case at.DISCOVERY_STREAM_PREFS_SETUP: return { ...prevState, recentSavesEnabled: action.data.recentSavesEnabled, pocketButtonEnabled: action.data.pocketButtonEnabled, saveToPocketCard: action.data.saveToPocketCard, hideDescriptions: action.data.hideDescriptions, compactImages: action.data.compactImages, imageGradient: action.data.imageGradient, newSponsoredLabel: action.data.newSponsoredLabel, titleLines: action.data.titleLines, descLines: action.data.descLines, readTime: action.data.readTime }; case at.DISCOVERY_STREAM_RECENT_SAVES: return { ...prevState, recentSavesData: action.data.recentSaves }; case at.DISCOVERY_STREAM_POCKET_STATE_SET: return { ...prevState, isUserLoggedIn: action.data.isUserLoggedIn }; case at.HIDE_PRIVACY_INFO: return { ...prevState, isPrivacyInfoModalVisible: false }; case at.SHOW_PRIVACY_INFO: return { ...prevState, isPrivacyInfoModalVisible: true }; case at.DISCOVERY_STREAM_LAYOUT_RESET: return { ...INITIAL_STATE.DiscoveryStream, config: prevState.config }; case at.DISCOVERY_STREAM_FEEDS_UPDATE: return { ...prevState, feeds: { ...prevState.feeds, loaded: true } }; case at.DISCOVERY_STREAM_FEED_UPDATE: const newData = {}; newData[action.data.url] = action.data.feed; return { ...prevState, feeds: { ...prevState.feeds, data: { ...prevState.feeds.data, ...newData } } }; case at.DISCOVERY_STREAM_SPOCS_CAPS: return { ...prevState, spocs: { ...prevState.spocs, frequency_caps: [...prevState.spocs.frequency_caps, ...action.data] } }; case at.DISCOVERY_STREAM_SPOCS_ENDPOINT: return { ...prevState, spocs: { ...INITIAL_STATE.DiscoveryStream.spocs, spocs_endpoint: action.data.url || INITIAL_STATE.DiscoveryStream.spocs.spocs_endpoint } }; case at.DISCOVERY_STREAM_SPOCS_PLACEMENTS: return { ...prevState, spocs: { ...prevState.spocs, placements: action.data.placements || INITIAL_STATE.DiscoveryStream.spocs.placements } }; case at.DISCOVERY_STREAM_SPOCS_UPDATE: if (action.data) { return { ...prevState, spocs: { ...prevState.spocs, lastUpdated: action.data.lastUpdated, data: action.data.spocs, loaded: true } }; } return prevState; case at.DISCOVERY_STREAM_SPOC_BLOCKED: return { ...prevState, spocs: { ...prevState.spocs, blocked: [...prevState.spocs.blocked, action.data.url] } }; case at.DISCOVERY_STREAM_LINK_BLOCKED: return isNotReady() ? prevState : nextState(items => items.filter(item => item.url !== action.data.url)); case at.PLACES_SAVED_TO_POCKET: const addPocketInfo = item => { if (item.url === action.data.url) { return Object.assign({}, item, { open_url: action.data.open_url, pocket_id: action.data.pocket_id, context_type: "pocket" }); } return item; }; return isNotReady() ? prevState : nextState(items => items.map(addPocketInfo)); case at.DELETE_FROM_POCKET: case at.ARCHIVE_FROM_POCKET: return isNotReady() ? prevState : nextState(items => items.filter(item => item.pocket_id !== action.data.pocket_id)); case at.PLACES_BOOKMARK_ADDED: const updateBookmarkInfo = item => { if (item.url === action.data.url) { const { bookmarkGuid, bookmarkTitle, dateAdded } = action.data; return Object.assign({}, item, { bookmarkGuid, bookmarkTitle, bookmarkDateCreated: dateAdded, context_type: "bookmark" }); } return item; }; return isNotReady() ? prevState : nextState(items => items.map(updateBookmarkInfo)); case at.PLACES_BOOKMARKS_REMOVED: const removeBookmarkInfo = item => { if (action.data.urls.includes(item.url)) { const newSite = Object.assign({}, item); delete newSite.bookmarkGuid; delete newSite.bookmarkTitle; delete newSite.bookmarkDateCreated; if (!newSite.context_type || newSite.context_type === "bookmark") { newSite.context_type = "removedBookmark"; } return newSite; } return item; }; return isNotReady() ? prevState : nextState(items => items.map(removeBookmarkInfo)); case at.PREF_CHANGED: if (action.data.name === PREF_COLLECTION_DISMISSIBLE) { return { ...prevState, isCollectionDismissible: action.data.value }; } return prevState; default: return prevState; } } function Search(prevState = INITIAL_STATE.Search, action) { switch (action.type) { case at.DISABLE_SEARCH: return Object.assign({ ...prevState, disable: true }); case at.FAKE_FOCUS_SEARCH: return Object.assign({ ...prevState, fakeFocus: true }); case at.SHOW_SEARCH: return Object.assign({ ...prevState, disable: false, fakeFocus: false }); default: return prevState; } } const reducers = { TopSites, App, ASRouter, Snippets, Prefs, Dialog, Sections, Pocket, Personalization, DiscoveryStream, Search }; module.exports = { reducers, INITIAL_STATE, insertPinned, TOP_SITES_DEFAULT_ROWS, TOP_SITES_MAX_SITES_PER_ROW }; /***/ }), /* 3 */ /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "Dedupe": () => (/* binding */ Dedupe) /* harmony export */ }); /* 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 Dedupe { constructor(createKey) { this.createKey = createKey || this.defaultCreateKey; } defaultCreateKey(item) { return item; } /** * Dedupe any number of grouped elements favoring those from earlier groups. * * @param {Array} groups Contains an arbitrary number of arrays of elements. * @returns {Array} A matching array of each provided group deduped. */ group(...groups) { const globalKeys = new Set(); const result = []; for (const values of groups) { const valueMap = new Map(); for (const value of values) { const key = this.createKey(value); if (!globalKeys.has(key) && !valueMap.has(key)) { valueMap.set(key, value); } } result.push(valueMap); valueMap.forEach((value, key) => globalKeys.add(key)); } return result.map(m => Array.from(m.values())); } } /***/ }) /******/ ]); /************************************************************************/ /******/ // The module cache /******/ var __webpack_module_cache__ = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ // Check if module is in cache /******/ var cachedModule = __webpack_module_cache__[moduleId]; /******/ if (cachedModule !== undefined) { /******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = __webpack_module_cache__[moduleId] = { /******/ // no module.id needed /******/ // no module.loaded needed /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /************************************************************************/ /******/ /* 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__ = {}; // This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk. (() => { // ESM COMPAT FLAG __webpack_require__.r(__webpack_exports__); // EXPORTS __webpack_require__.d(__webpack_exports__, { "NewTab": () => (/* binding */ NewTab), "renderCache": () => (/* binding */ renderCache), "renderWithoutState": () => (/* binding */ renderWithoutState) }); // NAMESPACE OBJECT: ./node_modules/fluent/src/builtins.js var builtins_namespaceObject = {}; __webpack_require__.r(builtins_namespaceObject); __webpack_require__.d(builtins_namespaceObject, { "DATETIME": () => (DATETIME), "NUMBER": () => (NUMBER) }); // EXTERNAL MODULE: ./common/Actions.sys.mjs var Actions_sys = __webpack_require__(1); ;// 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(Actions_sys.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/ASRouterAdmin.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 Row = props => /*#__PURE__*/external_React_default().createElement("tr", _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(Actions_sys.actionCreators.OnlyToMain({ type: Actions_sys.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(Actions_sys.actionCreators.OnlyToMain({ type: Actions_sys.actionTypes.DISCOVERY_STREAM_CONFIG_SET_VALUE, data: { name, value } })); } restorePrefDefaults(event) { this.props.dispatch(Actions_sys.actionCreators.OnlyToMain({ type: Actions_sys.actionTypes.DISCOVERY_STREAM_CONFIG_RESET_DEFAULTS })); } refreshCache() { const { config } = this.props.state.DiscoveryStream; this.props.dispatch(Actions_sys.actionCreators.OnlyToMain({ type: Actions_sys.actionTypes.DISCOVERY_STREAM_CONFIG_CHANGE, data: config })); } dispatchSimpleAction(type) { this.props.dispatch(Actions_sys.actionCreators.OnlyToMain({ type })); } systemTick() { this.dispatchSimpleAction(Actions_sys.actionTypes.DISCOVERY_STREAM_DEV_SYSTEM_TICK); } expireCache() { this.dispatchSimpleAction(Actions_sys.actionTypes.DISCOVERY_STREAM_DEV_EXPIRE_CACHE); } idleDaily() { this.dispatchSimpleAction(Actions_sys.actionTypes.DISCOVERY_STREAM_DEV_IDLE_DAILY); } syncRemoteSettings() { this.dispatchSimpleAction(Actions_sys.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) { 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.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); 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"), /*#__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.")), /*#__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, _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/src/types.js /* global Intl */ /** * 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 an `FluentType` instance. * * @param {Any} value - JavaScript value to wrap. * @param {Object} opts - Configuration. * @returns {FluentType} */ constructor(value, opts) { this.value = value; this.opts = opts; } /** * Unwrap the raw value stored by this `FluentType`. * * @returns {Any} */ valueOf() { return this.value; } /** * Format this instance of `FluentType` to a string. * * Formatted values are suitable for use outside of the `FluentBundle`. * This method can use `Intl` formatters memoized by the `FluentBundle` * instance passed as an argument. * * @param {FluentBundle} [bundle] * @returns {string} */ toString() { throw new Error("Subclasses of FluentType must implement toString."); } } class FluentNone extends FluentType { valueOf() { return null; } toString() { return `{${this.value || "???"}}`; } } class FluentNumber extends FluentType { constructor(value, opts) { super(parseFloat(value), opts); } toString(bundle) { try { const nf = bundle._memoizeIntlObject(Intl.NumberFormat, this.opts); return nf.format(this.value); } catch (e) { // XXX Report the error. return this.value; } } } class FluentDateTime extends FluentType { constructor(value, opts) { super(new Date(value), opts); } toString(bundle) { try { const dtf = bundle._memoizeIntlObject(Intl.DateTimeFormat, this.opts); return dtf.format(this.value); } catch (e) { // XXX Report the error. return this.value; } } } ;// CONCATENATED MODULE: ./node_modules/fluent/src/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 * `FluentType`. Functions must return `FluentType` objects as well. */ function merge(argopts, opts) { return Object.assign({}, argopts, values(opts)); } function values(opts) { const unwrapped = {}; for (const [name, opt] of Object.entries(opts)) { unwrapped[name] = opt.valueOf(); } return unwrapped; } function NUMBER([arg], opts) { if (arg instanceof FluentNone) { return arg; } if (arg instanceof FluentNumber) { return new FluentNumber(arg.valueOf(), merge(arg.opts, opts)); } return new FluentNone("NUMBER()"); } function DATETIME([arg], opts) { if (arg instanceof FluentNone) { return arg; } if (arg instanceof FluentDateTime) { return new FluentDateTime(arg.valueOf(), merge(arg.opts, opts)); } return new FluentNone("DATETIME()"); } ;// CONCATENATED MODULE: ./node_modules/fluent/src/resolver.js /* global Intl */ /** * @overview * * The role of the Fluent resolver is to format a translation object to an * instance of `FluentType` or an array of instances. * * 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, dates, lists and more into the * bundle's language. 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 `FluentType`. The caller should * use the `toString` method to convert the instance to a native value. * * All functions in this file pass around a special object called `scope`. * This object stores a set of elements used by all resolve functions: * * * {FluentBundle} bundle * bundle for which the given resolution is happening * * {Object} args * list of developer provided arguments that can be used * * {Array} errors * list of errors collected while resolving * * {WeakSet} dirty * Set of patterns already encountered during this resolution. * This is used to prevent cyclic resolutions. */ // Prevent expansion of too long placeables. const MAX_PLACEABLE_LENGTH = 2500; // Unicode bidi isolation characters. const FSI = "\u2068"; const PDI = "\u2069"; // Helper: match a variant key to the given selector. function match(bundle, 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 = bundle._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 Type(scope, variants[star]); } scope.errors.push(new RangeError("No default")); return new FluentNone(); } // Helper: resolve arguments to a call expression. function getArguments(scope, args) { const positional = []; const named = {}; for (const arg of args) { if (arg.type === "narg") { named[arg.name] = Type(scope, arg.value); } else { positional.push(Type(scope, arg)); } } return [positional, named]; } // Resolve an expression to a Fluent type. function Type(scope, expr) { // A fast-path for strings which are the most common case. Since they // natively have the `toString` method they can be used as if they were // a FluentType instance without incurring the cost of creating one. if (typeof expr === "string") { return scope.bundle._transform(expr); } // A fast-path for `FluentNone` which doesn't require any additional logic. if (expr instanceof FluentNone) { return expr; } // The Runtime AST (Entries) encodes patterns (complex strings with // placeables) as Arrays. if (Array.isArray(expr)) { return Pattern(scope, expr); } switch (expr.type) { case "str": return expr.value; case "num": return new FluentNumber(expr.value, { minimumFractionDigits: expr.precision }); case "var": return VariableReference(scope, expr); case "mesg": return MessageReference(scope, expr); case "term": return TermReference(scope, expr); case "func": return FunctionReference(scope, expr); case "select": return SelectExpression(scope, expr); case undefined: { // If it's a node with a value, resolve the value. if (expr.value !== null && expr.value !== undefined) { return Type(scope, expr.value); } scope.errors.push(new RangeError("No value")); return new FluentNone(); } default: return new FluentNone(); } } // Resolve a reference to a variable. function VariableReference(scope, { name }) { if (!scope.args || !scope.args.hasOwnProperty(name)) { if (scope.insideTermReference === false) { scope.errors.push(new ReferenceError(`Unknown variable: ${name}`)); } return new FluentNone(`$${name}`); } const arg = scope.args[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); } default: scope.errors.push(new TypeError(`Unsupported variable type: ${name}, ${typeof arg}`)); return new FluentNone(`$${name}`); } } // Resolve a reference to another message. function MessageReference(scope, { name, attr }) { const message = scope.bundle._messages.get(name); if (!message) { const err = new ReferenceError(`Unknown message: ${name}`); scope.errors.push(err); return new FluentNone(name); } if (attr) { const attribute = message.attrs && message.attrs[attr]; if (attribute) { return Type(scope, attribute); } scope.errors.push(new ReferenceError(`Unknown attribute: ${attr}`)); return new FluentNone(`${name}.${attr}`); } return Type(scope, message); } // Resolve a call to a Term with key-value arguments. function TermReference(scope, { name, attr, args }) { const id = `-${name}`; const term = scope.bundle._terms.get(id); if (!term) { const err = new ReferenceError(`Unknown term: ${id}`); scope.errors.push(err); return new FluentNone(id); } // Every TermReference has its own args. const [, keyargs] = getArguments(scope, args); const local = { ...scope, args: keyargs, insideTermReference: true }; if (attr) { const attribute = term.attrs && term.attrs[attr]; if (attribute) { return Type(local, attribute); } scope.errors.push(new ReferenceError(`Unknown attribute: ${attr}`)); return new FluentNone(`${id}.${attr}`); } return Type(local, term); } // Resolve a call to a Function with positional and key-value arguments. function FunctionReference(scope, { name, args }) { // Some functions are built-in. Others may be provided by the runtime via // the `FluentBundle` constructor. const func = scope.bundle._functions[name] || builtins_namespaceObject[name]; if (!func) { scope.errors.push(new ReferenceError(`Unknown function: ${name}()`)); return new FluentNone(`${name}()`); } if (typeof func !== "function") { scope.errors.push(new TypeError(`Function ${name}() is not callable`)); return new FluentNone(`${name}()`); } try { return func(...getArguments(scope, args)); } catch (e) { // XXX Report errors. return new FluentNone(`${name}()`); } } // Resolve a select expression to the member object. function SelectExpression(scope, { selector, variants, star }) { let sel = Type(scope, selector); if (sel instanceof FluentNone) { const variant = getDefault(scope, variants, star); return Type(scope, variant); } // Match the selector against keys of each variant, in order. for (const variant of variants) { const key = Type(scope, variant.key); if (match(scope.bundle, sel, key)) { return Type(scope, variant); } } const variant = getDefault(scope, variants, star); return Type(scope, variant); } // Resolve a pattern (a complex string with placeables). function Pattern(scope, ptn) { if (scope.dirty.has(ptn)) { scope.errors.push(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; } const part = Type(scope, elem).toString(scope.bundle); if (useIsolating) { result.push(FSI); } if (part.length > MAX_PLACEABLE_LENGTH) { scope.errors.push(new RangeError("Too many characters in placeable " + `(${part.length}, max allowed is ${MAX_PLACEABLE_LENGTH})`)); result.push(part.slice(MAX_PLACEABLE_LENGTH)); } else { result.push(part); } if (useIsolating) { result.push(PDI); } } scope.dirty.delete(ptn); return result.join(""); } /** * Format a translation into a string. * * @param {FluentBundle} bundle * A FluentBundle instance which will be used to resolve the * contextual information of the message. * @param {Object} args * List of arguments provided by the developer which can be accessed * from the message. * @param {Object} message * An object with the Message to be resolved. * @param {Array} errors * An error array that any encountered errors will be appended to. * @returns {FluentType} */ function resolve(bundle, args, message, errors = []) { const scope = { bundle, args, errors, dirty: new WeakSet(), // TermReferences are resolved in a new scope. insideTermReference: false }; return Type(scope, message).toString(bundle); } ;// CONCATENATED MODULE: ./node_modules/fluent/src/error.js class FluentError extends Error {} ;// CONCATENATED MODULE: ./node_modules/fluent/src/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-]*) *= */mg; // 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; // Maximum number of placeables in a single Pattern to protect against Quadratic // Blowup attacks. See https://msdn.microsoft.com/en-us/magazine/ee335713.aspx. const MAX_PLACEABLES = 100; /** * Fluent Resource is a structure storing a map of parsed localization entries. */ class FluentResource extends Map { /** * Create a new FluentResource from Fluent code. */ static fromString(source) { RE_MESSAGE_START.lastIndex = 0; let resource = new this(); 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 { resource.set(next[1], parseMessage()); } catch (err) { if (err instanceof FluentError) { // Don't report any Fluent syntax errors. Skip directly to the // beginning of the next message or term. continue; } throw err; } } return resource; // The parser implementation is inlined below for performance reasons. // 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 FluentError(`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() { let value = parsePattern(); let attrs = parseAttributes(); if (attrs === null) { if (value === null) { throw new FluentError("Expected message value or attributes"); } return value; } return { value, attrs }; } function parseAttributes() { let attrs = {}; while (test(RE_ATTRIBUTE_START)) { let name = match1(RE_ATTRIBUTE_START); let value = parsePattern(); if (value === null) { throw new FluentError("Expected attribute value"); } attrs[name] = value; } return Object.keys(attrs).length > 0 ? attrs : null; } function parsePattern() { // First try to parse any simple text on the same line as the id. if (test(RE_TEXT_RUN)) { var 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) { let placeableCount = 0; while (true) { if (test(RE_TEXT_RUN)) { elements.push(match1(RE_TEXT_RUN)); continue; } if (source[cursor] === "{") { if (++placeableCount > MAX_PLACEABLES) { throw new FluentError("Too many placeables"); } elements.push(parsePlaceable()); continue; } if (source[cursor] === "}") { throw new FluentError("Unbalanced closing brace"); } let indent = parseIndent(); if (indent) { elements.push(indent); commonIndent = Math.min(commonIndent, indent.length); continue; } break; } let lastIndex = elements.length - 1; // Trim the trailing spaces in the last element if it's a TextElement. if (typeof elements[lastIndex] === "string") { elements[lastIndex] = trim(elements[lastIndex], RE_TRAILING_SPACES); } let baked = []; for (let element of elements) { if (element.type === "indent") { // Dedent indented lines by the maximum common indent. element = element.value.slice(0, element.value.length - commonIndent); } else if (element.type === "str") { // Optimize StringLiterals into their value. element = element.value; } if (element) { baked.push(element); } } return baked; } function parsePlaceable() { consumeToken(TOKEN_BRACE_OPEN, FluentError); let selector = parseInlineExpression(); if (consumeToken(TOKEN_BRACE_CLOSE)) { return selector; } if (consumeToken(TOKEN_ARROW)) { let variants = parseVariants(); consumeToken(TOKEN_BRACE_CLOSE, FluentError); return { type: "select", selector, ...variants }; } throw new FluentError("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 FluentError("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 FluentError("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 FluentError("Expected variant value"); } variants[count++] = { key, value }; } if (count === 0) { return null; } if (star === undefined) { throw new FluentError("Expected default variant"); } return { variants, star }; } function parseVariantKey() { consumeToken(TOKEN_BRACKET_OPEN, FluentError); let key = test(RE_NUMBER_LITERAL) ? parseNumberLiteral() : match1(RE_IDENTIFIER); consumeToken(TOKEN_BRACKET_CLOSE, FluentError); return key; } function parseLiteral() { if (test(RE_NUMBER_LITERAL)) { return parseNumberLiteral(); } if (source[cursor] === "\"") { return parseStringLiteral(); } throw new FluentError("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("\"", FluentError); 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 FluentError("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 FluentError("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"); let length = RE_INDENT.exec(blank)[1].length; return { type: "indent", value, length }; } } } ;// CONCATENATED MODULE: ./node_modules/fluent/src/bundle.js /** * Message bundles are single-language stores of translations. They are * responsible for parsing translation resources in the Fluent syntax and can * format translation units (entities) to strings. * * Always use `FluentBundle.format` to retrieve translation units from a * bundle. Translations can contain references to other entities 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, dates, lists and more into the * bundle's language. See the documentation of the Fluent syntax for more * information. */ 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: * * const bundle = new FluentBundle(locales); * * const bundle = new FluentBundle(locales, { useIsolating: false }); * * const 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. * * @param {string|Array} locales - Locale or locales of the bundle * @param {Object} [options] * @returns {FluentBundle} */ constructor(locales, { functions = {}, useIsolating = true, transform = v => v } = {}) { this.locales = Array.isArray(locales) ? locales : [locales]; this._terms = new Map(); this._messages = new Map(); this._functions = functions; this._useIsolating = useIsolating; this._transform = transform; this._intls = new WeakMap(); } /* * Return an iterator over public `[id, message]` pairs. * * @returns {Iterator} */ get messages() { return this._messages[Symbol.iterator](); } /* * Check if a message is present in the bundle. * * @param {string} id - The identifier of the message to check. * @returns {bool} */ hasMessage(id) { return this._messages.has(id); } /* * Return the internal representation of a message. * * The internal representation should only be used as an argument to * `FluentBundle.format`. * * @param {string} id - The identifier of the message to check. * @returns {Any} */ getMessage(id) { return this._messages.get(id); } /** * Add a translation resource to the bundle. * * The translation resource must use the Fluent syntax. It will be parsed by * the bundle and each translation unit (message) will be available in the * bundle by its identifier. * * bundle.addMessages('foo = Foo'); * bundle.getMessage('foo'); * * // Returns a raw representation of the 'foo' message. * * bundle.addMessages('bar = Bar'); * bundle.addMessages('bar = Newbar', { allowOverrides: true }); * bundle.getMessage('bar'); * * // Returns a raw representation of the 'bar' message: Newbar. * * Parsed entities should be formatted with the `format` method in case they * contain logic (references, select expressions etc.). * * Available options: * * - `allowOverrides` - boolean specifying whether it's allowed to override * an existing message or term with a new value. * Default: false * * @param {string} source - Text resource with translations. * @param {Object} [options] * @returns {Array} */ addMessages(source, options) { const res = FluentResource.fromString(source); return this.addResource(res, options); } /** * Add a translation resource to the bundle. * * The translation resource must be an instance of FluentResource, * e.g. parsed by `FluentResource.fromString`. * * let res = FluentResource.fromString("foo = Foo"); * bundle.addResource(res); * bundle.getMessage('foo'); * * // Returns a raw representation of the 'foo' message. * * let res = FluentResource.fromString("bar = Bar"); * bundle.addResource(res); * res = FluentResource.fromString("bar = Newbar"); * bundle.addResource(res, { allowOverrides: true }); * bundle.getMessage('bar'); * * // Returns a raw representation of the 'bar' message: Newbar. * * Parsed entities should be formatted with the `format` method in case they * contain logic (references, select expressions etc.). * * Available options: * * - `allowOverrides` - boolean specifying whether it's allowed to override * an existing message or term with a new value. * Default: false * * @param {FluentResource} res - FluentResource object. * @param {Object} [options] * @returns {Array} */ addResource(res, { allowOverrides = false } = {}) { const errors = []; for (const [id, value] of res) { if (id.startsWith("-")) { // Identifiers starting with a dash (-) define terms. Terms are private // and cannot be retrieved from FluentBundle. if (allowOverrides === false && this._terms.has(id)) { errors.push(`Attempt to override an existing term: "${id}"`); continue; } this._terms.set(id, value); } else { if (allowOverrides === false && this._messages.has(id)) { errors.push(`Attempt to override an existing message: "${id}"`); continue; } this._messages.set(id, value); } } return errors; } /** * Format a message to a string or null. * * Format a raw `message` from the bundle into a string (or a null if it has * a null value). `args` will be used to resolve references to variables * passed as arguments to the translation. * * In case of errors `format` 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. * * const errors = []; * bundle.addMessages('hello = Hello, { $name }!'); * const hello = bundle.getMessage('hello'); * bundle.format(hello, { name: 'Jane' }, errors); * * // Returns 'Hello, Jane!' and `errors` is empty. * * bundle.format(hello, undefined, errors); * * // Returns 'Hello, name!' and `errors` is now: * * [] * * @param {Object | string} message * @param {Object | undefined} args * @param {Array} errors * @returns {?string} */ format(message, args, errors) { // optimize entities which are simple strings with no attributes if (typeof message === "string") { return this._transform(message); } // optimize entities with null values if (message === null || message.value === null) { return null; } // optimize simple-string entities with attributes if (typeof message.value === "string") { return this._transform(message.value); } return resolve(this, args, message, errors); } _memoizeIntlObject(ctor, opts) { const cache = this._intls.get(ctor) || {}; const id = JSON.stringify(opts); if (!cache[id]) { cache[id] = new ctor(this.locales, opts); this._intls.set(ctor, cache); } return cache[id]; } } ;// CONCATENATED MODULE: ./node_modules/fluent/src/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.addMessages(`${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: external "PropTypes" const external_PropTypes_namespaceObject = PropTypes; var external_PropTypes_default = /*#__PURE__*/__webpack_require__.n(external_PropTypes_namespaceObject); ;// CONCATENATED MODULE: ./node_modules/fluent-sequence/src/map_sync.js /* * Synchronously map an identifier or an array of identifiers to the best * `FluentBundle` instance(s). * * @param {Iterable} iterable * @param {string|Array} ids * @returns {FluentBundle|Array} */ function mapBundleSync(iterable, ids) { if (!Array.isArray(ids)) { return getBundleForId(iterable, ids); } return ids.map( id => getBundleForId(iterable, id) ); } /* * Find the best `FluentBundle` with the translation for `id`. */ function getBundleForId(iterable, id) { for (const bundle of iterable) { if (bundle.hasMessage(id)) { return bundle; } } return null; } ;// CONCATENATED MODULE: ./node_modules/fluent-sequence/src/map_async.js /* * Asynchronously map an identifier or an array of identifiers to the best * `FluentBundle` instance(s). * * @param {AsyncIterable} iterable * @param {string|Array} ids * @returns {Promise>} */ async function mapBundleAsync(iterable, ids) { if (!Array.isArray(ids)) { for await (const bundle of iterable) { if (bundle.hasMessage(ids)) { return bundle; } } } let remainingCount = ids.length; const foundBundles = new Array(remainingCount).fill(null); for await (const bundle of iterable) { 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 contexts. if (remainingCount === 0) { return foundBundles; } } } return foundBundles; } ;// CONCATENATED MODULE: ./node_modules/fluent-sequence/src/index.js /* * @module fluent-sequence * @overview Manage ordered sequences of FluentBundles. */ ;// CONCATENATED MODULE: ./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/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/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."); } } /** * Synchronous iterator over the cached elements. * * Return a generator object implementing the iterator protocol over the * cached elements of the original (async or sync) iterable. */ [Symbol.iterator]() { const cached = this; let cur = 0; return { next() { if (cached.length === cur) { return {value: undefined, done: true}; } return cached[cur++]; } }; } /** * 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(await 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 && last.done) { break; } this.push(await 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/cached-iterable/src/index.mjs ;// CONCATENATED MODULE: ./node_modules/fluent-react/src/localization.js /* * `ReactLocalization` handles translation formatting and fallback. * * The current negotiated fallback chain of languages is stored in the * `ReactLocalization` instance in form of an iterable of `FluentBundle` * instances. This iterable is used to find the best existing translation for * a given identifier. * * `Localized` components must subscribe to the changes of the * `ReactLocalization`'s fallback chain. When the fallback chain changes (the * `bundles` iterable is set anew), all subscribed compontent must relocalize. * * The `ReactLocalization` class instances are exposed to `Localized` elements * via the `LocalizationProvider` component. */ class ReactLocalization { constructor(bundles) { this.bundles = CachedSyncIterable.from(bundles); this.subs = new Set(); } /* * Subscribe a `Localized` component to changes of `bundles`. */ subscribe(comp) { this.subs.add(comp); } /* * Unsubscribe a `Localized` component from `bundles` changes. */ unsubscribe(comp) { this.subs.delete(comp); } /* * Set a new `bundles` iterable and trigger the retranslation. */ setBundles(bundles) { this.bundles = CachedSyncIterable.from(bundles); // Update all subscribed Localized components. this.subs.forEach(comp => comp.relocalize()); } getBundle(id) { return mapBundleSync(this.bundles, id); } /* * Find a translation by `id` and format it to a string using `args`. */ getString(id, args, fallback) { const bundle = this.getBundle(id); if (bundle === null) { return fallback || id; } const msg = bundle.getMessage(id); return bundle.format(msg, args); } } function isReactLocalization(props, propName) { const prop = props[propName]; if (prop instanceof ReactLocalization) { return null; } return new Error(`The ${propName} context field must be an instance of ReactLocalization.`); } ;// CONCATENATED MODULE: ./node_modules/fluent-react/src/markup.js /* eslint-env browser */ 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