diff options
Diffstat (limited to 'browser/components/asrouter/content')
4 files changed, 2527 insertions, 0 deletions
diff --git a/browser/components/asrouter/content/asrouter-admin.bundle.js b/browser/components/asrouter/content/asrouter-admin.bundle.js new file mode 100644 index 0000000000..b92be649c6 --- /dev/null +++ b/browser/components/asrouter/content/asrouter-admin.bundle.js @@ -0,0 +1,1936 @@ +/*! + * + * NOTE: This file is generated by webpack from ASRouterAdmin.jsx + * using the npm bundle task. + * + */ +var ASRouterAdminRenderUtils; +/******/ (() => { // webpackBootstrap +/******/ "use strict"; +/******/ var __webpack_modules__ = ([ +/* 0 */, +/* 1 */ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "ASRouterUtils": () => (/* binding */ ASRouterUtils) +/* harmony export */ }); +/* harmony import */ var modules_ActorConstants_sys_mjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); +/* harmony import */ var common_Actions_sys_mjs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3); +/* 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: modules_ActorConstants_sys_mjs__WEBPACK_IMPORTED_MODULE_0__.MESSAGE_TYPE_HASH.BLOCK_MESSAGE_BY_ID, + data: { + id, + ...options + } + }); + }, + modifyMessageJson(content) { + return ASRouterUtils.sendMessage({ + type: modules_ActorConstants_sys_mjs__WEBPACK_IMPORTED_MODULE_0__.MESSAGE_TYPE_HASH.MODIFY_MESSAGE_JSON, + data: { + content + } + }); + }, + executeAction(button_action) { + return ASRouterUtils.sendMessage({ + type: modules_ActorConstants_sys_mjs__WEBPACK_IMPORTED_MODULE_0__.MESSAGE_TYPE_HASH.USER_ACTION, + data: button_action + }); + }, + unblockById(id) { + return ASRouterUtils.sendMessage({ + type: modules_ActorConstants_sys_mjs__WEBPACK_IMPORTED_MODULE_0__.MESSAGE_TYPE_HASH.UNBLOCK_MESSAGE_BY_ID, + data: { + id + } + }); + }, + blockBundle(bundle) { + return ASRouterUtils.sendMessage({ + type: modules_ActorConstants_sys_mjs__WEBPACK_IMPORTED_MODULE_0__.MESSAGE_TYPE_HASH.BLOCK_BUNDLE, + data: { + bundle + } + }); + }, + unblockBundle(bundle) { + return ASRouterUtils.sendMessage({ + type: modules_ActorConstants_sys_mjs__WEBPACK_IMPORTED_MODULE_0__.MESSAGE_TYPE_HASH.UNBLOCK_BUNDLE, + data: { + bundle + } + }); + }, + overrideMessage(id) { + return ASRouterUtils.sendMessage({ + type: modules_ActorConstants_sys_mjs__WEBPACK_IMPORTED_MODULE_0__.MESSAGE_TYPE_HASH.OVERRIDE_MESSAGE, + data: { + id + } + }); + }, + editState(key, value) { + return ASRouterUtils.sendMessage({ + type: modules_ActorConstants_sys_mjs__WEBPACK_IMPORTED_MODULE_0__.MESSAGE_TYPE_HASH.EDIT_STATE, + data: { + [key]: value + } + }); + }, + sendTelemetry(ping) { + return ASRouterUtils.sendMessage(common_Actions_sys_mjs__WEBPACK_IMPORTED_MODULE_1__.actionCreators.ASRouterUserEvent(ping)); + }, + getPreviewEndpoint() { + return null; + } +}; + +/***/ }), +/* 2 */ +/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => { + +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "MESSAGE_TYPE_LIST": () => (/* binding */ MESSAGE_TYPE_LIST), +/* harmony export */ "MESSAGE_TYPE_HASH": () => (/* binding */ MESSAGE_TYPE_HASH) +/* harmony export */ }); +/* 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", + // 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", + "RESET_MESSAGE_STATE", + "RESET_SCREEN_IMPRESSIONS", + "EDIT_STATE", +]; + +const MESSAGE_TYPE_HASH = MESSAGE_TYPE_LIST.reduce((hash, value) => { + hash[value] = value; + return hash; +}, {}); + + +/***/ }), +/* 3 */ +/***/ ((__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_OVERRIDE", + "DISCOVERY_STREAM_PERSONALIZATION_RESET", + "DISCOVERY_STREAM_PERSONALIZATION_TOGGLE", + "DISCOVERY_STREAM_PERSONALIZATION_UPDATED", + "DISCOVERY_STREAM_POCKET_STATE_INIT", + "DISCOVERY_STREAM_POCKET_STATE_SET", + "DISCOVERY_STREAM_PREFS_SETUP", + "DISCOVERY_STREAM_RECENT_SAVES", + "DISCOVERY_STREAM_RETRY_FEED", + "DISCOVERY_STREAM_SPOCS_CAPS", + "DISCOVERY_STREAM_SPOCS_ENDPOINT", + "DISCOVERY_STREAM_SPOCS_PLACEMENTS", + "DISCOVERY_STREAM_SPOCS_UPDATE", + "DISCOVERY_STREAM_SPOC_BLOCKED", + "DISCOVERY_STREAM_SPOC_IMPRESSION", + "DISCOVERY_STREAM_USER_EVENT", + "DOWNLOAD_CHANGED", + "FAKE_FOCUS_SEARCH", + "FILL_SEARCH_TERM", + "HANDOFF_SEARCH_TO_AWESOMEBAR", + "HIDE_PERSONALIZE", + "HIDE_PRIVACY_INFO", + "INIT", + "NEW_TAB_INIT", + "NEW_TAB_INITIAL_STATE", + "NEW_TAB_LOAD", + "NEW_TAB_REHYDRATED", + "NEW_TAB_STATE_REQUEST", + "NEW_TAB_UNLOAD", + "OPEN_DOWNLOAD_FILE", + "OPEN_LINK", + "OPEN_NEW_WINDOW", + "OPEN_PRIVATE_WINDOW", + "OPEN_WEBEXT_SETTINGS", + "PARTNER_LINK_ATTRIBUTION", + "PLACES_BOOKMARKS_REMOVED", + "PLACES_BOOKMARK_ADDED", + "PLACES_HISTORY_CLEARED", + "PLACES_LINKS_CHANGED", + "PLACES_LINKS_DELETED", + "PLACES_LINK_BLOCKED", + "PLACES_SAVED_TO_POCKET", + "POCKET_CTA", + "POCKET_LINK_DELETED_OR_ARCHIVED", + "POCKET_LOGGED_IN", + "POCKET_WAITING_FOR_SPOC", + "PREFS_INITIAL_VALUES", + "PREF_CHANGED", + "PREVIEW_REQUEST", + "PREVIEW_REQUEST_CANCEL", + "PREVIEW_RESPONSE", + "REMOVE_DOWNLOAD_FILE", + "RICH_ICON_MISSING", + "SAVE_SESSION_PERF_DATA", + "SAVE_TO_POCKET", + "SCREENSHOT_UPDATED", + "SECTION_DEREGISTER", + "SECTION_DISABLE", + "SECTION_ENABLE", + "SECTION_MOVE", + "SECTION_OPTIONS_CHANGED", + "SECTION_REGISTER", + "SECTION_UPDATE", + "SECTION_UPDATE_CARD", + "SETTINGS_CLOSE", + "SETTINGS_OPEN", + "SET_PREF", + "SHOW_DOWNLOAD_FILE", + "SHOW_FIREFOX_ACCOUNTS", + "SHOW_PERSONALIZE", + "SHOW_PRIVACY_INFO", + "SHOW_SEARCH", + "SKIPPED_SIGNIN", + "SOV_UPDATED", + "SUBMIT_EMAIL", + "SUBMIT_SIGNIN", + "SYSTEM_TICK", + "TELEMETRY_IMPRESSION_STATS", + "TELEMETRY_USER_EVENT", + "TOP_SITES_CANCEL_EDIT", + "TOP_SITES_CLOSE_SEARCH_SHORTCUTS_MODAL", + "TOP_SITES_EDIT", + "TOP_SITES_INSERT", + "TOP_SITES_OPEN_SEARCH_SHORTCUTS_MODAL", + "TOP_SITES_ORGANIC_IMPRESSION_STATS", + "TOP_SITES_PIN", + "TOP_SITES_PREFS_UPDATED", + "TOP_SITES_SPONSORED_IMPRESSION_STATS", + "TOP_SITES_UNPIN", + "TOP_SITES_UPDATED", + "TOTAL_BOOKMARKS_REQUEST", + "TOTAL_BOOKMARKS_RESPONSE", + "UNINIT", + "UPDATE_PINNED_SEARCH_SHORTCUTS", + "UPDATE_SEARCH_SHORTCUTS", + "UPDATE_SECTION_PREFS", + "WEBEXT_CLICK", + "WEBEXT_DISMISS", +]) { + actionTypes[type] = type; +} + +// Helper function for creating routed actions between content and main +// Not intended to be used by consumers +function _RouteMessage(action, options) { + const meta = action.meta ? { ...action.meta } : {}; + if (!options || !options.from || !options.to) { + throw new Error( + "Routed Messages must have options as the second parameter, and must at least include a .from and .to property." + ); + } + // For each of these fields, if they are passed as an option, + // add them to the action. If they are not defined, remove them. + ["from", "to", "toTarget", "fromTarget", "skipMain", "skipLocal"].forEach( + o => { + if (typeof options[o] !== "undefined") { + meta[o] = options[o]; + } else if (meta[o]) { + delete meta[o]; + } + } + ); + return { ...action, meta }; +} + +/** + * AlsoToMain - Creates a message that will be dispatched locally and also sent to the Main process. + * + * @param {object} action Any redux action (required) + * @param {object} options + * @param {bool} skipLocal Used by OnlyToMain to skip the main reducer + * @param {string} fromTarget The id of the content port from which the action originated. (optional) + * @return {object} An action with added .meta properties + */ +function AlsoToMain(action, fromTarget, skipLocal) { + return _RouteMessage(action, { + from: CONTENT_MESSAGE_TYPE, + to: MAIN_MESSAGE_TYPE, + fromTarget, + skipLocal, + }); +} + +/** + * OnlyToMain - Creates a message that will be sent to the Main process and skip the local reducer. + * + * @param {object} action Any redux action (required) + * @param {object} options + * @param {string} fromTarget The id of the content port from which the action originated. (optional) + * @return {object} An action with added .meta properties + */ +function OnlyToMain(action, fromTarget) { + return AlsoToMain(action, fromTarget, true); +} + +/** + * BroadcastToContent - Creates a message that will be dispatched to main and sent to ALL content processes. + * + * @param {object} action Any redux action (required) + * @return {object} An action with added .meta properties + */ +function BroadcastToContent(action) { + return _RouteMessage(action, { + from: MAIN_MESSAGE_TYPE, + to: CONTENT_MESSAGE_TYPE, + }); +} + +/** + * AlsoToOneContent - Creates a message that will be will be dispatched to the main store + * and also sent to a particular Content process. + * + * @param {object} action Any redux action (required) + * @param {string} target The id of a content port + * @param {bool} skipMain Used by OnlyToOneContent to skip the main process + * @return {object} An action with added .meta properties + */ +function AlsoToOneContent(action, target, skipMain) { + if (!target) { + throw new Error( + "You must provide a target ID as the second parameter of AlsoToOneContent. If you want to send to all content processes, use BroadcastToContent" + ); + } + return _RouteMessage(action, { + from: MAIN_MESSAGE_TYPE, + to: CONTENT_MESSAGE_TYPE, + toTarget: target, + skipMain, + }); +} + +/** + * OnlyToOneContent - Creates a message that will be sent to a particular Content process + * and skip the main reducer. + * + * @param {object} action Any redux action (required) + * @param {string} target The id of a content port + * @return {object} An action with added .meta properties + */ +function OnlyToOneContent(action, target) { + return AlsoToOneContent(action, target, true); +} + +/** + * AlsoToPreloaded - Creates a message that dispatched to the main reducer and also sent to the preloaded tab. + * + * @param {object} action Any redux action (required) + * @return {object} An action with added .meta properties + */ +function AlsoToPreloaded(action) { + return _RouteMessage(action, { + from: MAIN_MESSAGE_TYPE, + to: PRELOAD_MESSAGE_TYPE, + }); +} + +/** + * UserEvent - A telemetry ping indicating a user action. This should only + * be sent from the UI during a user session. + * + * @param {object} data Fields to include in the ping (source, etc.) + * @return {object} An AlsoToMain action + */ +function UserEvent(data) { + return AlsoToMain({ + type: actionTypes.TELEMETRY_USER_EVENT, + data, + }); +} + +/** + * DiscoveryStreamUserEvent - A telemetry ping indicating a user action from Discovery Stream. This should only + * be sent from the UI during a user session. + * + * @param {object} data Fields to include in the ping (source, etc.) + * @return {object} An AlsoToMain action + */ +function DiscoveryStreamUserEvent(data) { + return AlsoToMain({ + type: actionTypes.DISCOVERY_STREAM_USER_EVENT, + data, + }); +} + +/** + * ASRouterUserEvent - A telemetry ping indicating a user action from AS router. This should only + * be sent from the UI during a user session. + * + * @param {object} data Fields to include in the ping (source, etc.) + * @return {object} An AlsoToMain action + */ +function ASRouterUserEvent(data) { + return AlsoToMain({ + type: actionTypes.AS_ROUTER_TELEMETRY_USER_EVENT, + data, + }); +} + +/** + * ImpressionStats - A telemetry ping indicating an impression stats. + * + * @param {object} data Fields to include in the ping + * @param {int} importContext (For testing) Override the import context for testing. + * #return {object} An action. For UI code, a AlsoToMain action. + */ +function ImpressionStats(data, importContext = globalImportContext) { + const action = { + type: actionTypes.TELEMETRY_IMPRESSION_STATS, + data, + }; + return importContext === UI_CODE ? AlsoToMain(action) : action; +} + +/** + * DiscoveryStreamImpressionStats - A telemetry ping indicating an impression stats in Discovery Stream. + * + * @param {object} data Fields to include in the ping + * @param {int} importContext (For testing) Override the import context for testing. + * #return {object} An action. For UI code, a AlsoToMain action. + */ +function DiscoveryStreamImpressionStats( + data, + importContext = globalImportContext +) { + const action = { + type: actionTypes.DISCOVERY_STREAM_IMPRESSION_STATS, + data, + }; + return importContext === UI_CODE ? AlsoToMain(action) : action; +} + +/** + * DiscoveryStreamLoadedContent - A telemetry ping indicating a content gets loaded in Discovery Stream. + * + * @param {object} data Fields to include in the ping + * @param {int} importContext (For testing) Override the import context for testing. + * #return {object} An action. For UI code, a AlsoToMain action. + */ +function DiscoveryStreamLoadedContent( + data, + importContext = globalImportContext +) { + const action = { + type: actionTypes.DISCOVERY_STREAM_LOADED_CONTENT, + data, + }; + return importContext === UI_CODE ? AlsoToMain(action) : action; +} + +function SetPref(name, value, importContext = globalImportContext) { + const action = { type: actionTypes.SET_PREF, data: { name, value } }; + return importContext === UI_CODE ? AlsoToMain(action) : action; +} + +function WebExtEvent(type, data, importContext = globalImportContext) { + if (!data || !data.source) { + throw new Error( + 'WebExtEvent actions should include a property "source", the id of the webextension that should receive the event.' + ); + } + const action = { type, data }; + return importContext === UI_CODE ? AlsoToMain(action) : action; +} + +const actionCreators = { + BroadcastToContent, + UserEvent, + DiscoveryStreamUserEvent, + ASRouterUserEvent, + ImpressionStats, + AlsoToOneContent, + OnlyToOneContent, + AlsoToMain, + OnlyToMain, + AlsoToPreloaded, + SetPref, + WebExtEvent, + DiscoveryStreamImpressionStats, + DiscoveryStreamLoadedContent, +}; + +// These are helpers to test for certain kinds of actions +const actionUtils = { + isSendToMain(action) { + if (!action.meta) { + return false; + } + return ( + action.meta.to === MAIN_MESSAGE_TYPE && + action.meta.from === CONTENT_MESSAGE_TYPE + ); + }, + isBroadcastToContent(action) { + if (!action.meta) { + return false; + } + if (action.meta.to === CONTENT_MESSAGE_TYPE && !action.meta.toTarget) { + return true; + } + return false; + }, + isSendToOneContent(action) { + if (!action.meta) { + return false; + } + if (action.meta.to === CONTENT_MESSAGE_TYPE && action.meta.toTarget) { + return true; + } + return false; + }, + isSendToPreloaded(action) { + if (!action.meta) { + return false; + } + return ( + action.meta.to === PRELOAD_MESSAGE_TYPE && + action.meta.from === MAIN_MESSAGE_TYPE + ); + }, + isFromMain(action) { + if (!action.meta) { + return false; + } + return ( + action.meta.from === MAIN_MESSAGE_TYPE && + action.meta.to === CONTENT_MESSAGE_TYPE + ); + }, + getPortIdOfSender(action) { + return (action.meta && action.meta.fromTarget) || null; + }, + _RouteMessage, +}; + + +/***/ }), +/* 4 */ +/***/ ((module) => { + +module.exports = React; + +/***/ }), +/* 5 */ +/***/ ((module) => { + +module.exports = ReactDOM; + +/***/ }), +/* 6 */ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "SimpleHashRouter": () => (/* binding */ SimpleHashRouter) +/* harmony export */ }); +/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4); +/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__); +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +class SimpleHashRouter extends (react__WEBPACK_IMPORTED_MODULE_0___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__*/react__WEBPACK_IMPORTED_MODULE_0___default().cloneElement(this.props.children, { + location: { + hash: this.state.hash, + routes + } + }); + } +} + +/***/ }), +/* 7 */ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "CopyButton": () => (/* binding */ CopyButton) +/* harmony export */ }); +/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4); +/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__); +function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +const CopyButton = ({ + className, + label, + copiedLabel, + inputSelector, + transformer, + ...props +}) => { + const [copied, setCopied] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false); + const timeout = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null); + const onClick = (0,react__WEBPACK_IMPORTED_MODULE_0__.useCallback)(() => { + let text = document.querySelector(inputSelector).value; + if (transformer) { + text = transformer(text); + } + navigator.clipboard.writeText(text); + clearTimeout(timeout.current); + setCopied(true); + timeout.current = setTimeout(() => setCopied(false), 1500); + }, [inputSelector, transformer]); + return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", _extends({ + className: className, + onClick: e => onClick() + }, props), copied && copiedLabel || label); +}; + +/***/ }), +/* 8 */ +/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { + +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "ImpressionsSection": () => (/* binding */ ImpressionsSection) +/* harmony export */ }); +/* harmony import */ var _asrouter_utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1); +/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); +/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_1__); +/* 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 stringify = json => JSON.stringify(json, null, 2); +const ImpressionsSection = ({ + messageImpressions, + groupImpressions, + screenImpressions +}) => { + const handleSaveMessageImpressions = (0,react__WEBPACK_IMPORTED_MODULE_1__.useCallback)(newImpressions => { + _asrouter_utils__WEBPACK_IMPORTED_MODULE_0__.ASRouterUtils.editState("messageImpressions", newImpressions); + }, []); + const handleSaveGroupImpressions = (0,react__WEBPACK_IMPORTED_MODULE_1__.useCallback)(newImpressions => { + _asrouter_utils__WEBPACK_IMPORTED_MODULE_0__.ASRouterUtils.editState("groupImpressions", newImpressions); + }, []); + const handleSaveScreenImpressions = (0,react__WEBPACK_IMPORTED_MODULE_1__.useCallback)(newImpressions => { + _asrouter_utils__WEBPACK_IMPORTED_MODULE_0__.ASRouterUtils.editState("screenImpressions", newImpressions); + }, []); + const handleResetMessageImpressions = (0,react__WEBPACK_IMPORTED_MODULE_1__.useCallback)(() => { + _asrouter_utils__WEBPACK_IMPORTED_MODULE_0__.ASRouterUtils.sendMessage({ + type: "RESET_MESSAGE_STATE" + }); + }, []); + const handleResetGroupImpressions = (0,react__WEBPACK_IMPORTED_MODULE_1__.useCallback)(() => { + _asrouter_utils__WEBPACK_IMPORTED_MODULE_0__.ASRouterUtils.sendMessage({ + type: "RESET_GROUPS_STATE" + }); + }, []); + const handleResetScreenImpressions = (0,react__WEBPACK_IMPORTED_MODULE_1__.useCallback)(() => { + _asrouter_utils__WEBPACK_IMPORTED_MODULE_0__.ASRouterUtils.sendMessage({ + type: "RESET_SCREEN_IMPRESSIONS" + }); + }, []); + return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("div", { + className: "impressions-section" + }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement(ImpressionsItem, { + impressions: messageImpressions, + label: "Message Impressions", + description: "Message impressions are stored in an object, where each key is a message ID and each value is an array of timestamps. They are cleaned up when a message with that ID stops existing in ASRouter state (such as at the end of an experiment).", + onSave: handleSaveMessageImpressions, + onReset: handleResetMessageImpressions + }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement(ImpressionsItem, { + impressions: groupImpressions, + label: "Group Impressions", + description: "Group impressions are stored in an object, where each key is a group ID and each value is an array of timestamps. They are never cleaned up.", + onSave: handleSaveGroupImpressions, + onReset: handleResetGroupImpressions + }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement(ImpressionsItem, { + impressions: screenImpressions, + label: "Screen Impressions", + description: "Screen impressions are stored in an object, where each key is a screen ID and each value is the most recent timestamp that screen was shown. They are never cleaned up.", + onSave: handleSaveScreenImpressions, + onReset: handleResetScreenImpressions + })); +}; +const ImpressionsItem = ({ + impressions, + label, + description, + validator, + onSave, + onReset +}) => { + const [json, setJson] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(stringify(impressions)); + const modified = (0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)(false); + const isValidJson = (0,react__WEBPACK_IMPORTED_MODULE_1__.useCallback)(text => { + try { + JSON.parse(text); + return validator ? validator(text) : true; + } catch (e) { + return false; + } + }, [validator]); + const jsonIsInvalid = (0,react__WEBPACK_IMPORTED_MODULE_1__.useMemo)(() => !isValidJson(json), [json, isValidJson]); + const handleChange = (0,react__WEBPACK_IMPORTED_MODULE_1__.useCallback)(e => { + setJson(e.target.value); + modified.current = true; + }, []); + const handleSave = (0,react__WEBPACK_IMPORTED_MODULE_1__.useCallback)(() => { + if (jsonIsInvalid) { + return; + } + const newImpressions = JSON.parse(json); + modified.current = false; + onSave(newImpressions); + }, [json, jsonIsInvalid, onSave]); + const handleReset = (0,react__WEBPACK_IMPORTED_MODULE_1__.useCallback)(() => { + modified.current = false; + onReset(); + }, [onReset]); + (0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(() => { + if (!modified.current) { + setJson(stringify(impressions)); + } + }, [impressions]); + return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("div", { + className: "impressions-item" + }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("span", { + className: "impressions-category" + }, label), description ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("p", { + className: "impressions-description" + }, description) : null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("div", { + className: "impressions-inner-box" + }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("div", { + className: "impressions-buttons" + }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("button", { + className: "button primary", + disabled: jsonIsInvalid, + onClick: handleSave + }, "Save"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("button", { + className: "button reset", + onClick: handleReset + }, "Reset")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("div", { + className: "impressions-editor" + }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("textarea", { + className: "general-textarea", + value: json, + onChange: handleChange + })))); +}; + +/***/ }) +/******/ ]); +/************************************************************************/ +/******/ // 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. +(() => { +__webpack_require__.r(__webpack_exports__); +/* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ "ToggleStoryButton": () => (/* binding */ ToggleStoryButton), +/* harmony export */ "ToggleMessageJSON": () => (/* binding */ ToggleMessageJSON), +/* harmony export */ "TogglePrefCheckbox": () => (/* binding */ TogglePrefCheckbox), +/* harmony export */ "ASRouterAdminInner": () => (/* binding */ ASRouterAdminInner), +/* harmony export */ "ASRouterAdmin": () => (/* binding */ ASRouterAdmin), +/* harmony export */ "renderASRouterAdmin": () => (/* binding */ renderASRouterAdmin) +/* harmony export */ }); +/* harmony import */ var _asrouter_utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1); +/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); +/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(5); +/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(react_dom__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var _SimpleHashRouter__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6); +/* harmony import */ var _CopyButton__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(7); +/* harmony import */ var _ImpressionsSection__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(8); +function _extends() { _extends = Object.assign ? Object.assign.bind() : 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__*/react__WEBPACK_IMPORTED_MODULE_1___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(); +} +class ToggleStoryButton extends (react__WEBPACK_IMPORTED_MODULE_1___default().PureComponent) { + constructor(props) { + super(props); + this.handleClick = this.handleClick.bind(this); + } + handleClick() { + this.props.onClick(this.props.story); + } + render() { + return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("button", { + onClick: this.handleClick + }, "collapse/open"); + } +} +class ToggleMessageJSON extends (react__WEBPACK_IMPORTED_MODULE_1___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__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("button", { + className: "clearButton", + onClick: this.handleClick + }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("span", { + className: iconName + })); + } +} +class TogglePrefCheckbox extends (react__WEBPACK_IMPORTED_MODULE_1___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__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement((react__WEBPACK_IMPORTED_MODULE_1___default().Fragment), null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("input", { + type: "checkbox", + checked: this.props.checked, + onChange: this.onChange, + disabled: this.props.disabled + }), " ", this.props.pref, " "); + } +} +class ASRouterAdminInner extends (react__WEBPACK_IMPORTED_MODULE_1___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() { + _asrouter_utils__WEBPACK_IMPORTED_MODULE_0__.ASRouterUtils.addListener(this.onMessageFromParent); + const endpoint = _asrouter_utils__WEBPACK_IMPORTED_MODULE_0__.ASRouterUtils.getPreviewEndpoint(); + _asrouter_utils__WEBPACK_IMPORTED_MODULE_0__.ASRouterUtils.sendMessage({ + type: "ADMIN_CONNECT_STATE", + data: { + endpoint + } + }).then(this.setStateFromParent); + } + componentWillUnmount() { + _asrouter_utils__WEBPACK_IMPORTED_MODULE_0__.ASRouterUtils.removeListener(this.onMessageFromParent); + } + handleBlock(msg) { + return () => _asrouter_utils__WEBPACK_IMPORTED_MODULE_0__.ASRouterUtils.blockById(msg.id); + } + handleUnblock(msg) { + return () => _asrouter_utils__WEBPACK_IMPORTED_MODULE_0__.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 () => _asrouter_utils__WEBPACK_IMPORTED_MODULE_0__.ASRouterUtils.overrideMessage(id).then(state => { + this.setStateFromParent(state); + }); + } + 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?.id) { + _asrouter_utils__WEBPACK_IMPORTED_MODULE_0__.ASRouterUtils.blockById(message.id); + _asrouter_utils__WEBPACK_IMPORTED_MODULE_0__.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() { + _asrouter_utils__WEBPACK_IMPORTED_MODULE_0__.ASRouterUtils.sendMessage({ + type: "FORCE_PRIVATE_BROWSING_WINDOW", + data: { + message: { + content: this.state.selectedPBMessage + } + } + }); + } + expireCache() { + _asrouter_utils__WEBPACK_IMPORTED_MODULE_0__.ASRouterUtils.sendMessage({ + type: "EXPIRE_QUERY_CACHE" + }); + } + resetPref() { + _asrouter_utils__WEBPACK_IMPORTED_MODULE_0__.ASRouterUtils.sendMessage({ + type: "RESET_PROVIDER_PREF" + }); + } + resetGroups(id, value) { + _asrouter_utils__WEBPACK_IMPORTED_MODULE_0__.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; + } + _asrouter_utils__WEBPACK_IMPORTED_MODULE_0__.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 _asrouter_utils__WEBPACK_IMPORTED_MODULE_0__.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; + _asrouter_utils__WEBPACK_IMPORTED_MODULE_0__.ASRouterUtils.sendMessage({ + type: "DISABLE_PROVIDER", + data: providerId + }); + if (!isUserEnabled) { + _asrouter_utils__WEBPACK_IMPORTED_MODULE_0__.ASRouterUtils.sendMessage({ + type: "SET_PROVIDER_USER_PREF", + data: { + id: providerId, + value: true + } + }); + } + _asrouter_utils__WEBPACK_IMPORTED_MODULE_0__.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) { + _asrouter_utils__WEBPACK_IMPORTED_MODULE_0__.ASRouterUtils.sendMessage({ + type: "SET_PROVIDER_USER_PREF", + data: { + id: provider.id, + value: true + } + }); + } + if (!isSystemEnabled) { + _asrouter_utils__WEBPACK_IMPORTED_MODULE_0__.ASRouterUtils.sendMessage({ + type: "ENABLE_PROVIDER", + data: provider.id + }); + } + } else { + _asrouter_utils__WEBPACK_IMPORTED_MODULE_0__.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 + } + }; + _asrouter_utils__WEBPACK_IMPORTED_MODULE_0__.ASRouterUtils.sendMessage(action); + this.setState({ + messageFilter: "all" + }); + } + onChangeMessageFilter(event) { + this.setState({ + messageFilter: event.target.value + }); + } + onChangeMessageGroupsFilter(event) { + this.setState({ + messageGroupsFilter: event.target.value + }); + } + + // Simulate a copy event that sets to clipboard all targeting paramters and values + onCopyTargetingParams(event) { + const stringTargetingParameters = { + ...this.state.stringTargetingParameters + }; + for (const key of Object.keys(stringTargetingParameters)) { + // If the value is not set the parameter will be lost when we stringify + if (stringTargetingParameters[key] === undefined) { + stringTargetingParameters[key] = null; + } + } + const setClipboardData = e => { + e.preventDefault(); + e.clipboardData.setData("text", JSON.stringify(stringTargetingParameters, null, 2)); + document.removeEventListener("copy", setClipboardData); + this.setState({ + copiedToClipboard: true + }); + }; + document.addEventListener("copy", setClipboardData); + document.execCommand("copy"); + } + onNewTargetingParams(event) { + this.setState({ + newStringTargetingParameters: event.target.value + }); + event.target.classList.remove("errorState"); + this.refs.targetingParamsEval.innerText = ""; + try { + const stringTargetingParameters = JSON.parse(event.target.value); + this.setState({ + stringTargetingParameters + }); + } catch (e) { + event.target.classList.add("errorState"); + this.refs.targetingParamsEval.innerText = e.message; + } + } + toggleJSON(msgId) { + if (this.state.collapsedMessages.includes(msgId)) { + let index = this.state.collapsedMessages.indexOf(msgId); + this.setState(prevState => ({ + collapsedMessages: [...prevState.collapsedMessages.slice(0, index), ...prevState.collapsedMessages.slice(index + 1)] + })); + } else { + this.setState(prevState => ({ + collapsedMessages: prevState.collapsedMessages.concat(msgId) + })); + } + } + handleChange(msgId) { + if (!this.state.modifiedMessages.includes(msgId)) { + this.setState(prevState => ({ + modifiedMessages: prevState.modifiedMessages.concat(msgId) + })); + } + } + renderMessageItem(msg) { + const isBlockedByGroup = this.state.groups.filter(group => msg.groups.includes(group.id)).some(group => !group.enabled); + const msgProvider = this.state.providers.find(provider => provider.id === msg.provider) || {}; + const isProviderExcluded = msgProvider.exclude && msgProvider.exclude.includes(msg.id); + const isMessageBlocked = this.state.messageBlockList.includes(msg.id) || this.state.messageBlockList.includes(msg.campaign); + const isBlocked = isMessageBlocked || isBlockedByGroup || isProviderExcluded; + const impressions = this.state.messageImpressions[msg.id] ? this.state.messageImpressions[msg.id].length : 0; + const isCollapsed = this.state.collapsedMessages.includes(msg.id); + const isModified = this.state.modifiedMessages.includes(msg.id); + const aboutMessagePreviewSupported = ["infobar", "spotlight", "cfr_doorhanger"].includes(msg.template); + let itemClassName = "message-item"; + if (isBlocked) { + itemClassName += " blocked"; + } + return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("tr", { + className: itemClassName, + key: `${msg.id}-${msg.provider}` + }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", { + className: "message-id" + }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("span", null, msg.id, " ", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("br", null))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement(ToggleMessageJSON, { + msgId: `${msg.id}`, + toggleJSON: this.toggleJSON, + isCollapsed: isCollapsed + })), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", { + className: "button-column" + }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___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__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("button", { + className: "button restore", + onClick: e => this.resetJSON(msg) + }, "Reset") : /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("button", { + className: "button show", + onClick: this.handleOverride(msg.id) + }, "Show"), isBlocked ? null : /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("button", { + className: "button modify", + onClick: e => this.modifyJson(msg) + }, "Modify"), aboutMessagePreviewSupported ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement(_CopyButton__WEBPACK_IMPORTED_MODULE_4__.CopyButton, { + transformer: text => `about:messagepreview?json=${encodeURIComponent(btoa(text))}`, + label: "Share", + copiedLabel: "Copied!", + inputSelector: `#${msg.id}-textarea`, + className: "button share" + }) : null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("br", null), "(", impressions, " impressions)"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", { + className: "message-summary" + }, isBlocked && /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("tr", null, "Block reason:", isBlockedByGroup && " Blocked by group", isProviderExcluded && " Excluded by provider", isMessageBlocked && " Message blocked"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("tr", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("pre", { + className: isCollapsed ? "collapsed" : "expanded" + }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("textarea", { + id: `${msg.id}-textarea`, + name: msg.id, + className: "general-textarea", + disabled: isBlocked, + 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?.content + }); + } else { + this.setState({ + selectedPBMessage: "" + }); + } + } + } + modifyJson(content) { + const message = JSON.parse(document.getElementById(`${content.id}-textarea`).value); + return _asrouter_utils__WEBPACK_IMPORTED_MODULE_0__.ASRouterUtils.modifyMessageJson(message).then(state => { + this.setStateFromParent(state); + }); + } + 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__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("tr", { + className: itemClassName, + key: `${msg.id}-${msg.provider}` + }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", { + className: "message-id" + }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("span", null, msg.id, " ", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("br", null), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("br", null), "(", impressions, " impressions)")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement(ToggleMessageJSON, { + msgId: `${msg.id}`, + toggleJSON: this.toggleJSON, + isCollapsed: isCollapsed + })), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("input", { + type: "radio", + id: `${msg.id} radio`, + name: "PB_message_radio", + style: { + marginBottom: 20 + }, + onClick: () => this.selectPBMessage(msg.id), + disabled: isBlocked + }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("button", { + className: `button ${isBlocked ? "" : " primary"}`, + onClick: isBlocked ? this.handleUnblock(msg) : this.handleBlock(msg) + }, isBlocked ? "Unblock" : "Block"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("button", { + className: "ASRouterButton slim button", + onClick: e => this.resetPBJSON(msg) + }, "Reset JSON")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", { + className: `message-summary` + }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("pre", { + className: isCollapsed ? "collapsed" : "expanded" + }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___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__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("div", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("button", { + className: "ASRouterButton slim", + onClick: e => this.toggleAllMessages(messagesToShow) + }, "Collapse/Expand All"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("p", { + className: "helpLink" + }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("span", { + className: "icon icon-small-spacer icon-info" + }), " ", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("span", null, "To modify a message, change the JSON and click 'Modify' to see your changes. Click 'Reset' to restore the JSON to the original. Click 'Share' to copy a link to the clipboard that can be used to preview the message by opening the link in Nightly/local builds.")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("table", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___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__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("table", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___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__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("table", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("tbody", null, messagesToShow.map(msg => this.renderPBMessageItem(msg)))); + } + renderMessageFilter() { + if (!this.state.providers) { + return null; + } + return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("p", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("button", { + className: "unblock-all ASRouterButton test-only", + onClick: this.unblockAll + }, "Unblock All Snippets"), "Show messages from", " ", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("select", { + value: this.state.messageFilter, + onChange: this.onChangeMessageFilter + }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("option", { + value: "all" + }, "all providers"), this.state.providers.map(provider => /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("option", { + key: provider.id, + value: provider.id + }, provider.id))), this.state.messageFilter !== "all" && !this.state.messageFilter.includes("_local_testing") ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("button", { + className: "button messages-reset", + onClick: this.handleClearAllImpressionsByProvider + }, "Reset All") : null); + } + renderMessageGroupsFilter() { + if (!this.state.groups) { + return null; + } + return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("p", null, "Show messages from ", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("select", { + value: this.state.messageGroupsFilter, + onChange: this.onChangeMessageGroupsFilter + }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("option", { + value: "all" + }, "all groups"), this.state.groups.map(group => /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("option", { + key: group.id, + value: group.id + }, group.id)))); + } + renderTableHead() { + return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("thead", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("tr", { + className: "message-item" + }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", { + className: "min" + }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", { + className: "min" + }, "Provider ID"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, "Source"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", { + className: "min" + }, "Cohort"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___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__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("table", null, this.renderTableHead(), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___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__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("span", null, "endpoint (", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___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__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("span", null, "remote settings (", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___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__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("tr", { + className: "message-item", + key: i + }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, isTestProvider ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("input", { + type: "checkbox", + disabled: true, + readOnly: true, + checked: true + }) : /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("input", { + type: "checkbox", + "data-provider": provider.id, + checked: isUserEnabled && isSystemEnabled, + onChange: this.handleEnabledToggle + })), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, provider.id), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("span", { + className: `sourceLabel${isUserEnabled && isSystemEnabled ? "" : " isDisabled"}` + }, label)), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, provider.cohort), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___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__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("table", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("tbody", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("tr", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("h2", null, "Evaluate JEXL expression"))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("tr", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("p", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("textarea", { + ref: "expressionInput", + rows: "10", + cols: "60", + placeholder: "Evaluate JEXL expressions and mock parameters by changing their values below" + })), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("p", null, "Status:", " ", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("span", { + ref: "evaluationStatus" + }, success ? "✅" : "❌", ", Result: ", result))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("button", { + className: "ASRouterButton secondary", + onClick: this.handleExpressionEval + }, "Evaluate"))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("tr", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("h2", null, "Modify targeting parameters"))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("tr", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___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__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("textarea", { + name: param, + className: className, + value: value, + rows: "10", + cols: "60", + onChange: this.onChangeTargetingParameters + }) : /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("input", { + name: param, + className: className, + value: value, + onChange: this.onChangeTargetingParameters + }); + return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("tr", { + key: i + }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, param), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___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) { + _asrouter_utils__WEBPACK_IMPORTED_MODULE_0__.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"; + } + renderAttributionParamers() { + return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("div", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("h2", null, " Attribution Parameters "), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___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__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("table", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("tr", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("b", null, " Source ")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, " ", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("input", { + type: "text", + name: "source", + placeholder: "addons.mozilla.org", + value: this.state.attributionParameters.source, + onChange: this.onChangeAttributionParameters + }), " ")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("tr", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("b", null, " Medium ")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, " ", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("input", { + type: "text", + name: "medium", + placeholder: "referral", + value: this.state.attributionParameters.medium, + onChange: this.onChangeAttributionParameters + }), " ")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("tr", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("b", null, " Campaign ")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, " ", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("input", { + type: "text", + name: "campaign", + placeholder: "non-fx-button", + value: this.state.attributionParameters.campaign, + onChange: this.onChangeAttributionParameters + }), " ")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("tr", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("b", null, " Content ")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, " ", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("input", { + type: "text", + name: "content", + placeholder: `rta:${btoa("uBlock0@raymondhill.net")}`, + value: this.state.attributionParameters.content, + onChange: this.onChangeAttributionParameters + }), " ")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("tr", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("b", null, " Experiment ")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, " ", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("input", { + type: "text", + name: "experiment", + placeholder: "ua-onboarding", + value: this.state.attributionParameters.experiment, + onChange: this.onChangeAttributionParameters + }), " ")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("tr", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("b", null, " Variation ")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, " ", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("input", { + type: "text", + name: "variation", + placeholder: "chrome", + value: this.state.attributionParameters.variation, + onChange: this.onChangeAttributionParameters + }), " ")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("tr", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("b", null, " User Agent ")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, " ", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("input", { + type: "text", + name: "ua", + placeholder: "Google Chrome 123", + value: this.state.attributionParameters.ua, + onChange: this.onChangeAttributionParameters + }), " ")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("tr", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("b", null, " Download Token ")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, " ", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("input", { + type: "text", + name: "dltoken", + placeholder: "00000000-0000-0000-0000-000000000000", + value: this.state.attributionParameters.dltoken, + onChange: this.onChangeAttributionParameters + }), " ")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("tr", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, " ", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("button", { + className: "ASRouterButton primary button", + onClick: this.setAttribution + }, " ", "Force Attribution", " "), " ")))); + } + renderErrorMessage({ + id, + errors + }) { + const providerId = /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", { + rowSpan: errors.length + }, id); + // .reverse() so that the last error (most recent) is first + return errors.map(({ + error, + timestamp + }, cellKey) => /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("tr", { + key: cellKey + }, cellKey === errors.length - 1 ? providerId : null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, error.message), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___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__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("table", { + className: "errorReporting" + }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("thead", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("tr", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("th", null, "Provider ID"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("th", null, "Message"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("th", null, "Timestamp"))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("tbody", null, providersWithErrors.map(this.renderErrorMessage))); + } + return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___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__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("div", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("p", { + className: "helpLink" + }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("span", { + className: "icon icon-small-spacer icon-info" + }), " ", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("span", null, "To view an available message, select its radio button and click \"Open a Private Browsing Window\".", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___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__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("br", null), "Click \"Reset Message State\" to clear all message impressions and view messages in a clean state.", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("br", null), "Note that ContentSearch functions do not work in debug mode.")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("div", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("button", { + className: "ASRouterButton primary button", + onClick: this.handleOpenPB + }, "Open a Private Browsing Window"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("button", { + className: "ASRouterButton primary button", + style: { + marginInlineStart: 12 + }, + onClick: this.resetPBMessageState + }, "Reset Message State"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("br", null), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("input", { + type: "radio", + id: `clear radio`, + name: "PB_message_radio", + value: "clearPBMessage", + style: { + display: "none" + } + }), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("h2", null, "Messages"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("button", { + className: "ASRouterButton slim button", + onClick: e => this.toggleAllMessages(messagesToShow) + }, "Collapse/Expand All"), this.renderPBMessages())); + } + getSection() { + const [section] = this.props.location.routes; + switch (section) { + case "private": + return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement((react__WEBPACK_IMPORTED_MODULE_1___default().Fragment), null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("h2", null, "Private Browsing Messages"), this.renderPBTab()); + case "targeting": + return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement((react__WEBPACK_IMPORTED_MODULE_1___default().Fragment), null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("h2", null, "Targeting Utilities"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___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__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement((react__WEBPACK_IMPORTED_MODULE_1___default().Fragment), null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("h2", null, "Message Groups"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("button", { + className: "button", + onClick: this.resetGroups + }, "Reset group impressions"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("table", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("thead", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("tr", { + className: "message-item" + }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, "Enabled"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, "Impressions count"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, "Custom frequency"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, "User preferences"))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("tbody", null, this.state.groups && this.state.groups.map(({ + id, + enabled, + frequency, + userPreferences = [] + }, index) => /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement(Row, { + key: id + }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement(TogglePrefCheckbox, { + checked: enabled, + pref: id, + disabled: true + })), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, this._getGroupImpressionsCount(id, frequency)), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, JSON.stringify(frequency, null, 2)), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("td", null, userPreferences.join(", ")))))), this.renderMessageGroupsFilter(), this.renderMessagesByGroup()); + case "impressions": + return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement((react__WEBPACK_IMPORTED_MODULE_1___default().Fragment), null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("h2", null, "Impressions"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement(_ImpressionsSection__WEBPACK_IMPORTED_MODULE_5__.ImpressionsSection, { + messageImpressions: this.state.messageImpressions, + groupImpressions: this.state.groupImpressions, + screenImpressions: this.state.screenImpressions + })); + case "errors": + return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement((react__WEBPACK_IMPORTED_MODULE_1___default().Fragment), null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("h2", null, "ASRouter Errors"), this.renderErrors()); + default: + return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement((react__WEBPACK_IMPORTED_MODULE_1___default().Fragment), null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("h2", null, "Message Providers", " ", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___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__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("h2", null, "Messages"), this.renderMessageFilter(), this.renderMessages()); + } + } + render() { + if (!this.state.devtoolsEnabled) { + return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("div", { + className: "asrouter-admin" + }, "You must enable the ASRouter Admin page by setting", " ", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("code", null, "browser.newtabpage.activity-stream.asrouter.devtoolsEnabled"), " ", "to ", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("code", null, "true"), " and then reloading this page."); + } + return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("div", { + className: `asrouter-admin ${this.props.collapsed ? "collapsed" : "expanded"}` + }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("aside", { + className: "sidebar" + }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("ul", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("li", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("a", { + href: "#devtools" + }, "General")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("li", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("a", { + href: "#devtools-private" + }, "Private Browsing")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("li", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("a", { + href: "#devtools-targeting" + }, "Targeting")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("li", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("a", { + href: "#devtools-groups" + }, "Message Groups")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("li", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("a", { + href: "#devtools-impressions" + }, "Impressions")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("li", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("a", { + href: "#devtools-errors" + }, "Errors")))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("main", { + className: "main-panel" + }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("h1", null, "AS Router Admin"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("p", { + className: "helpLink" + }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("span", { + className: "icon icon-small-spacer icon-info" + }), " ", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement("span", null, "Need help using these tools? Check out our", " ", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___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())); + } +} +const ASRouterAdmin = props => /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement(_SimpleHashRouter__WEBPACK_IMPORTED_MODULE_3__.SimpleHashRouter, null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement(ASRouterAdminInner, props)); +function renderASRouterAdmin() { + react_dom__WEBPACK_IMPORTED_MODULE_2___default().render( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement(ASRouterAdmin, null), document.getElementById("root")); +} +})(); + +ASRouterAdminRenderUtils = __webpack_exports__; +/******/ })() +;
\ No newline at end of file diff --git a/browser/components/asrouter/content/asrouter-admin.html b/browser/components/asrouter/content/asrouter-admin.html new file mode 100644 index 0000000000..3c5e0b378d --- /dev/null +++ b/browser/components/asrouter/content/asrouter-admin.html @@ -0,0 +1,38 @@ +<!-- 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/. --> + +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <meta + http-equiv="Content-Security-Policy" + content="default-src 'none'; object-src 'none'; script-src resource: chrome:;" + /> + <meta name="color-scheme" content="light dark" /> + <title>ASRouter Admin</title> + <link + rel="icon" + type="image/png" + href="chrome://branding/content/icon32.png" + /> + <link rel="localization" href="branding/brand.ftl" /> + <link rel="localization" href="toolkit/branding/brandings.ftl" /> + <link + rel="stylesheet" + href="chrome://browser/content/asrouter/components/ASRouterAdmin/ASRouterAdmin.css" + /> + </head> + <body> + <div id="root"></div> + <script src="chrome://browser/content/contentTheme.js"></script> + <script src="resource://activity-stream/vendor/react.js"></script> + <script src="resource://activity-stream/vendor/react-dom.js"></script> + <script src="resource://activity-stream/vendor/prop-types.js"></script> + <script src="chrome://browser/content/asrouter/asrouter-admin.bundle.js"></script> + + <!-- The render.js script is the main entrypoint for the page. --> + <script src="chrome://browser/content/asrouter/render.js"></script> + </body> +</html> diff --git a/browser/components/asrouter/content/components/ASRouterAdmin/ASRouterAdmin.css b/browser/components/asrouter/content/components/ASRouterAdmin/ASRouterAdmin.css new file mode 100644 index 0000000000..aaf15a9a15 --- /dev/null +++ b/browser/components/asrouter/content/components/ASRouterAdmin/ASRouterAdmin.css @@ -0,0 +1,546 @@ +/* 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/. */ +/* stylelint-disable max-nesting-depth */ +:root { + --newtab-background-color: #F9F9FB; + --newtab-background-color-secondary: #FFF; + --newtab-text-primary-color: #15141a; + --newtab-primary-action-background: #0061e0; + --newtab-primary-action-background-pocket: #008078; + --newtab-text-secondary-color: color-mix(in srgb, var(--newtab-text-primary-color) 70%, transparent); + --newtab-element-hover-color: color-mix(in srgb, var(--newtab-background-color) 90%, #000); + --newtab-element-active-color: color-mix(in srgb, var(--newtab-background-color) 80%, #000); + --newtab-element-secondary-color: color-mix(in srgb, currentColor 5%, transparent); + --newtab-element-secondary-hover-color: color-mix(in srgb, currentColor 12%, transparent); + --newtab-element-secondary-active-color: color-mix(in srgb, currentColor 25%, transparent); + --newtab-primary-element-hover-color: color-mix(in srgb, var(--newtab-primary-action-background) 90%, #000); + --newtab-primary-element-hover-pocket-color: color-mix(in srgb, var(--newtab-primary-action-background-pocket) 90%, #000); + --newtab-primary-element-active-color: color-mix(in srgb, var(--newtab-primary-action-background) 80%, #000); + --newtab-primary-element-text-color: #FFF; + --newtab-primary-action-background-dimmed: color-mix(in srgb, var(--newtab-primary-action-background) 25%, transparent); + --newtab-primary-action-background-pocket-dimmed: color-mix(in srgb, var(--newtab-primary-action-background-pocket) 25%, transparent); + --newtab-border-color: color-mix(in srgb, var(--newtab-background-color) 75%, #000); + --newtab-wordmark-color: #20123A; + --newtab-status-success: #058B00; + --newtab-status-error: #D70022; + --newtab-inner-box-shadow-color: rgba(0, 0, 0, 0.1); + --newtab-overlay-color: color-mix(in srgb, var(--newtab-background-color) 85%, transparent); + --newtab-textbox-focus-color: var(--newtab-primary-action-background); + --newtab-textbox-focus-boxshadow: 0 0 0 1px var(--newtab-primary-action-background), 0 0 0 4px rgba(var(--newtab-primary-action-background), 0.3); + --newtab-button-secondary-color: inherit; +} +:root[lwt-newtab-brighttext] { + --newtab-background-color: #2B2A33; + --newtab-background-color-secondary: #42414d; + --newtab-text-primary-color: #fbfbfe; + --newtab-primary-action-background: #00ddff; + --newtab-primary-action-background-pocket: #00ddff; + --newtab-primary-action-background-pocket-dimmed: color-mix(in srgb, var(--newtab-primary-action-background) 25%, transparent); + --newtab-primary-element-hover-color: color-mix(in srgb, var(--newtab-primary-action-background) 55%, #FFF); + --newtab-primary-element-hover-pocket-color: color-mix(in srgb, var(--newtab-primary-action-background-pocket) 55%, #FFF); + --newtab-element-hover-color: color-mix(in srgb, var(--newtab-background-color) 80%, #FFF); + --newtab-element-active-color: color-mix(in srgb, var(--newtab-background-color) 60%, #FFF); + --newtab-element-secondary-color: color-mix(in srgb, currentColor 10%, transparent); + --newtab-element-secondary-hover-color: color-mix(in srgb, currentColor 17%, transparent); + --newtab-element-secondary-active-color: color-mix(in srgb, currentColor 30%, transparent); + --newtab-border-color: color-mix(in srgb, var(--newtab-background-color) 75%, #FFF); + --newtab-primary-element-text-color: #2b2a33; + --newtab-wordmark-color: #fbfbfe; + --newtab-status-success: #7C6; +} + +@media (prefers-contrast) { + :root { + --newtab-text-secondary-color: var(--newtab-text-primary-color); + } +} +.icon { + background-position: center center; + background-repeat: no-repeat; + background-size: 16px; + -moz-context-properties: fill; + display: inline-block; + color: var(--newtab-text-primary-color); + fill: currentColor; + height: 16px; + vertical-align: middle; + width: 16px; +} +.icon.icon-spacer { + margin-inline-end: 8px; +} +.icon.icon-small-spacer { + margin-inline-end: 6px; +} +.icon.icon-button-style { + fill: var(--newtab-text-secondary-color); + border: 0; +} +.icon.icon-button-style:focus, .icon.icon-button-style:hover { + fill: var(--newtab-text-primary-color); +} +.icon.icon-bookmark-added { + background-image: url("chrome://browser/skin/bookmark.svg"); +} +.icon.icon-bookmark-hollow { + background-image: url("chrome://browser/skin/bookmark-hollow.svg"); +} +.icon.icon-clear-input { + background-image: url("chrome://global/skin/icons/close-fill.svg"); +} +.icon.icon-delete { + background-image: url("chrome://global/skin/icons/delete.svg"); +} +.icon.icon-search { + background-image: url("chrome://global/skin/icons/search-glass.svg"); +} +.icon.icon-modal-delete { + flex-shrink: 0; + background-image: url("chrome://activity-stream/content/data/content/assets/glyph-modal-delete-20.svg"); + background-size: 32px; + height: 32px; + width: 32px; +} +.icon.icon-mail { + background-image: url("chrome://activity-stream/content/data/content/assets/glyph-mail-16.svg"); +} +.icon.icon-dismiss { + background-image: url("chrome://global/skin/icons/close.svg"); +} +.icon.icon-info { + background-image: url("chrome://global/skin/icons/info.svg"); +} +.icon.icon-new-window { + background-image: url("chrome://activity-stream/content/data/content/assets/glyph-newWindow-16.svg"); +} +.icon.icon-new-window:dir(rtl) { + transform: scaleX(-1); +} +.icon.icon-new-window-private { + background-image: url("chrome://browser/skin/privateBrowsing.svg"); +} +.icon.icon-settings { + background-image: url("chrome://global/skin/icons/settings.svg"); +} +.icon.icon-pin { + background-image: url("chrome://activity-stream/content/data/content/assets/glyph-pin-16.svg"); +} +.icon.icon-pin:dir(rtl) { + transform: scaleX(-1); +} +.icon.icon-unpin { + background-image: url("chrome://activity-stream/content/data/content/assets/glyph-unpin-16.svg"); +} +.icon.icon-unpin:dir(rtl) { + transform: scaleX(-1); +} +.icon.icon-edit { + background-image: url("chrome://global/skin/icons/edit.svg"); +} +.icon.icon-pocket { + background-image: url("chrome://global/skin/icons/pocket.svg"); +} +.icon.icon-pocket-save { + background-image: url("chrome://global/skin/icons/pocket.svg"); + fill: #FFF; +} +.icon.icon-pocket-delete { + background-image: url("chrome://activity-stream/content/data/content/assets/glyph-pocket-delete-16.svg"); +} +.icon.icon-pocket-archive { + background-image: url("chrome://activity-stream/content/data/content/assets/glyph-pocket-archive-16.svg"); +} +.icon.icon-history-item { + background-image: url("chrome://browser/skin/history.svg"); +} +.icon.icon-trending { + background-image: url("chrome://browser/skin/trending.svg"); + transform: translateY(2px); +} +.icon.icon-now { + background-image: url("chrome://browser/skin/history.svg"); +} +.icon.icon-topsites { + background-image: url("chrome://browser/skin/topsites.svg"); +} +.icon.icon-pin-small { + background-image: url("chrome://browser/skin/pin-12.svg"); + background-size: 12px; + height: 12px; + width: 12px; +} +.icon.icon-pin-small:dir(rtl) { + transform: scaleX(-1); +} +.icon.icon-check { + background-image: url("chrome://global/skin/icons/check.svg"); +} +.icon.icon-download { + background-image: url("chrome://browser/skin/downloads/downloads.svg"); +} +.icon.icon-copy { + background-image: url("chrome://global/skin/icons/edit-copy.svg"); +} +.icon.icon-open-file { + background-image: url("chrome://activity-stream/content/data/content/assets/glyph-open-file-16.svg"); +} +.icon.icon-webextension { + background-image: url("chrome://activity-stream/content/data/content/assets/glyph-webextension-16.svg"); +} +.icon.icon-highlights { + background-image: url("chrome://global/skin/icons/highlights.svg"); +} +.icon.icon-arrowhead-down { + background-image: url("chrome://global/skin/icons/arrow-down.svg"); +} +.icon.icon-arrowhead-down-small { + background-image: url("chrome://global/skin/icons/arrow-down-12.svg"); + background-size: 12px; + height: 12px; + width: 12px; +} +.icon.icon-arrowhead-forward-small { + background-image: url("chrome://global/skin/icons/arrow-right-12.svg"); + background-size: 12px; + height: 12px; + width: 12px; +} +.icon.icon-arrowhead-forward-small:dir(rtl) { + background-image: url("chrome://global/skin/icons/arrow-left-12.svg"); +} +.icon.icon-arrowhead-up { + background-image: url("chrome://global/skin/icons/arrow-up.svg"); +} +.icon.icon-add { + background-image: url("chrome://global/skin/icons/plus.svg"); +} +.icon.icon-minimize { + background-image: url("chrome://activity-stream/content/data/content/assets/glyph-minimize-16.svg"); +} +.icon.icon-maximize { + background-image: url("chrome://activity-stream/content/data/content/assets/glyph-maximize-16.svg"); +} +.icon.icon-arrow { + background-image: url("chrome://global/skin/icons/arrow-right-12.svg"); +} + +.ASRouterButton { + font-weight: 600; + font-size: 14px; + white-space: nowrap; + border-radius: 2px; + border: 0; + font-family: inherit; + padding: 8px 15px; + margin-inline-start: 12px; + color: inherit; + cursor: pointer; +} +.tall .ASRouterButton { + margin-inline-start: 20px; +} +.ASRouterButton.test-only { + width: 0; + height: 0; + overflow: hidden; + display: block; + visibility: hidden; +} +.ASRouterButton.primary { + border: 1px solid var(--newtab-primary-action-background); + background-color: var(--newtab-primary-action-background); + color: var(--newtab-primary-element-text-color); +} +.ASRouterButton.primary:hover { + background-color: var(--newtab-primary-element-hover-color); +} +.ASRouterButton.primary:active { + background-color: var(--newtab-primary-element-active-color); +} +.ASRouterButton.slim { + border: 1px solid var(--newtab-border-color); + margin-inline-start: 0; + font-size: 12px; + padding: 6px 12px; +} +.ASRouterButton.slim:hover, .ASRouterButton.slim:focus { + box-shadow: 0 0 0 5px var(--newtab-element-secondary-color); + transition: box-shadow 150ms; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Ubuntu, "Helvetica Neue", sans-serif; +} + +/** + * These styles are copied verbatim from _activity-stream.scss in order to maintain + * a continuity of styling while also decoupling from the newtab code. This should + * be removed when about:asrouter starts using the default in-content style sheets. + */ +.button, +.actions button { + background-color: var(--newtab-button-secondary-color); + border: 1px solid var(--newtab-border-color); + border-radius: 4px; + color: inherit; + cursor: pointer; + margin-bottom: 15px; + padding: 10px 30px; + white-space: nowrap; +} +.button:hover:not(.dismiss), .button:focus:not(.dismiss), +.actions button:hover:not(.dismiss), +.actions button:focus:not(.dismiss) { + box-shadow: 0 0 0 5px var(--newtab-element-secondary-color); + transition: box-shadow 150ms; +} +.button.dismiss, +.actions button.dismiss { + background-color: transparent; + border: 0; + padding: 0; + text-decoration: underline; +} +.button.primary, .button.done, +.actions button.primary, +.actions button.done { + background-color: var(--newtab-primary-action-background); + border: solid 1px var(--newtab-primary-action-background); + color: var(--newtab-primary-element-text-color); + margin-inline-start: auto; +} + +.asrouter-admin { + max-width: 1300px; + font-size: 14px; + padding-inline-start: 240px; + color: var(--newtab-text-primary-color); +} +.asrouter-admin.collapsed { + display: none; +} +.asrouter-admin .sidebar { + inset-inline-start: 0; + position: fixed; + width: 240px; +} +.asrouter-admin .sidebar ul { + margin: 0; + padding: 0; + list-style: none; +} +.asrouter-admin .sidebar li a { + padding: 10px 34px; + display: block; + color: var(--lwt-sidebar-text-color); +} +.asrouter-admin .sidebar li a:hover { + background: var(--newtab-background-color-secondary); +} +.asrouter-admin h1 { + font-weight: 200; + font-size: 32px; +} +.asrouter-admin h2 .button, +.asrouter-admin p .button { + font-size: 14px; + padding: 6px 12px; + margin-inline-start: 5px; + margin-bottom: 0; +} +.asrouter-admin .general-textarea { + direction: ltr; + width: 740px; + height: 500px; + overflow: auto; + resize: none; + border-radius: 4px; + display: flex; +} +.asrouter-admin .wnp-textarea { + direction: ltr; + width: 740px; + height: 500px; + overflow: auto; + resize: none; + border-radius: 4px; + display: flex; +} +.asrouter-admin .json-button { + display: inline-flex; + font-size: 10px; + padding: 4px 10px; + margin-bottom: 6px; + margin-inline-end: 4px; +} +.asrouter-admin .json-button:hover { + background-color: var(--newtab-element-hover-color); + box-shadow: none; +} +.asrouter-admin table { + border-collapse: collapse; +} +.asrouter-admin table.minimal-table { + border-collapse: collapse; + border: 1px solid var(--newtab-border-color); +} +.asrouter-admin table.minimal-table td { + padding: 8px; +} +.asrouter-admin table.minimal-table td:first-child { + width: 1%; + white-space: nowrap; +} +.asrouter-admin table.minimal-table td:not(:first-child) { + font-family: "SF Mono", "Monaco", "Inconsolata", "Fira Mono", "Droid Sans Mono", "Source Code Pro", monospace; +} +.asrouter-admin table.errorReporting tr { + border: 1px solid var(--newtab-background-color-secondary); +} +.asrouter-admin table.errorReporting td { + padding: 4px; +} +.asrouter-admin table.errorReporting td[rowspan] { + border: 1px solid var(--newtab-background-color-secondary); +} +.asrouter-admin .sourceLabel { + background: var(--newtab-background-color-secondary); + padding: 2px 5px; + border-radius: 3px; +} +.asrouter-admin .sourceLabel.isDisabled { + background: rgba(215, 0, 34, 0.3); + color: var(--newtab-status-error); +} +.asrouter-admin .message-item:first-child td { + border-top: 1px solid var(--newtab-border-color); +} +.asrouter-admin .message-item td { + vertical-align: top; + padding: 8px; + border-bottom: 1px solid var(--newtab-border-color); +} +.asrouter-admin .message-item td.min { + width: 1%; + white-space: nowrap; +} +.asrouter-admin .message-item td.message-summary { + width: 60%; +} +.asrouter-admin .message-item td.button-column { + width: 15%; +} +.asrouter-admin .message-item td:first-child { + border-inline-start: 1px solid var(--newtab-border-color); +} +.asrouter-admin .message-item td:last-child { + border-inline-end: 1px solid var(--newtab-border-color); +} +.asrouter-admin .message-item.blocked .message-id, +.asrouter-admin .message-item.blocked .message-summary { + opacity: 0.5; +} +.asrouter-admin .message-item.blocked .message-id { + opacity: 0.5; +} +.asrouter-admin .message-item .message-id { + font-family: "SF Mono", "Monaco", "Inconsolata", "Fira Mono", "Droid Sans Mono", "Source Code Pro", monospace; + font-size: 12px; +} +.asrouter-admin .providerUrl { + font-size: 12px; +} +.asrouter-admin pre { + background: var(--newtab-background-color-secondary); + margin: 0; + padding: 8px; + font-size: 12px; + max-width: 750px; + overflow: auto; + font-family: "SF Mono", "Monaco", "Inconsolata", "Fira Mono", "Droid Sans Mono", "Source Code Pro", monospace; +} +.asrouter-admin .errorState { + border: 1px solid var(--newtab-status-error); +} +.asrouter-admin .helpLink { + padding: 10px; + display: flex; + background: rgba(0, 0, 0, 0.1); + border-radius: 3px; + align-items: center; +} +.asrouter-admin .helpLink a { + text-decoration: underline; +} +.asrouter-admin .helpLink .icon { + min-width: 18px; + min-height: 18px; +} +.asrouter-admin .ds-component { + margin-bottom: 20px; +} +.asrouter-admin .modalOverlayInner { + height: 80%; +} +.asrouter-admin .clearButton { + border: 0; + padding: 4px; + border-radius: 4px; + display: flex; +} +.asrouter-admin .clearButton:hover { + background: var(--newtab-element-hover-color); +} +.asrouter-admin .collapsed { + display: none; +} +.asrouter-admin .icon { + display: inline-table; + cursor: pointer; + width: 18px; + height: 18px; +} +.asrouter-admin .button:disabled, .asrouter-admin .button:disabled:active { + opacity: 0.5; + cursor: unset; + box-shadow: none; +} +.asrouter-admin .impressions-section { + display: flex; + flex-direction: column; + gap: 16px; +} +.asrouter-admin .impressions-section .impressions-item { + display: flex; + flex-flow: column nowrap; + padding: 8px; + border: 1px solid var(--newtab-border-color); + border-radius: 5px; +} +.asrouter-admin .impressions-section .impressions-item .impressions-inner-box { + display: flex; + flex-flow: row nowrap; + gap: 8px; +} +.asrouter-admin .impressions-section .impressions-item .impressions-category { + font-size: 1.15em; + white-space: nowrap; + flex-grow: 0.1; +} +.asrouter-admin .impressions-section .impressions-item .impressions-buttons { + display: flex; + flex-direction: column; + gap: 8px; +} +.asrouter-admin .impressions-section .impressions-item .impressions-buttons button { + margin: 0; +} +.asrouter-admin .impressions-section .impressions-item .impressions-editor { + display: flex; + flex-grow: 1.5; +} +.asrouter-admin .impressions-section .impressions-item .impressions-editor .general-textarea { + width: auto; + flex-grow: 1; +} diff --git a/browser/components/asrouter/content/render.js b/browser/components/asrouter/content/render.js new file mode 100644 index 0000000000..66d6a05d3b --- /dev/null +++ b/browser/components/asrouter/content/render.js @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +// exported by asrouter-admin.bundle.js +window.ASRouterAdminRenderUtils.renderASRouterAdmin(); |