tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

activity-stream.bundle.js (629777B)


      1 /*! THIS FILE IS AUTO-GENERATED: webpack.system-addon.config.js */
      2 var NewtabRenderUtils;
      3 /******/ (() => { // webpackBootstrap
      4 /******/ 	"use strict";
      5 /******/ 	// The require scope
      6 /******/ 	var __webpack_require__ = {};
      7 /******/ 	
      8 /************************************************************************/
      9 /******/ 	/* webpack/runtime/compat get default export */
     10 /******/ 	(() => {
     11 /******/ 		// getDefaultExport function for compatibility with non-harmony modules
     12 /******/ 		__webpack_require__.n = (module) => {
     13 /******/ 			var getter = module && module.__esModule ?
     14 /******/ 				() => (module['default']) :
     15 /******/ 				() => (module);
     16 /******/ 			__webpack_require__.d(getter, { a: getter });
     17 /******/ 			return getter;
     18 /******/ 		};
     19 /******/ 	})();
     20 /******/ 	
     21 /******/ 	/* webpack/runtime/define property getters */
     22 /******/ 	(() => {
     23 /******/ 		// define getter functions for harmony exports
     24 /******/ 		__webpack_require__.d = (exports, definition) => {
     25 /******/ 			for(var key in definition) {
     26 /******/ 				if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
     27 /******/ 					Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
     28 /******/ 				}
     29 /******/ 			}
     30 /******/ 		};
     31 /******/ 	})();
     32 /******/ 	
     33 /******/ 	/* webpack/runtime/global */
     34 /******/ 	(() => {
     35 /******/ 		__webpack_require__.g = (function() {
     36 /******/ 			if (typeof globalThis === 'object') return globalThis;
     37 /******/ 			try {
     38 /******/ 				return this || new Function('return this')();
     39 /******/ 			} catch (e) {
     40 /******/ 				if (typeof window === 'object') return window;
     41 /******/ 			}
     42 /******/ 		})();
     43 /******/ 	})();
     44 /******/ 	
     45 /******/ 	/* webpack/runtime/hasOwnProperty shorthand */
     46 /******/ 	(() => {
     47 /******/ 		__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
     48 /******/ 	})();
     49 /******/ 	
     50 /******/ 	/* webpack/runtime/make namespace object */
     51 /******/ 	(() => {
     52 /******/ 		// define __esModule on exports
     53 /******/ 		__webpack_require__.r = (exports) => {
     54 /******/ 			if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
     55 /******/ 				Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
     56 /******/ 			}
     57 /******/ 			Object.defineProperty(exports, '__esModule', { value: true });
     58 /******/ 		};
     59 /******/ 	})();
     60 /******/ 	
     61 /************************************************************************/
     62 var __webpack_exports__ = {};
     63 // ESM COMPAT FLAG
     64 __webpack_require__.r(__webpack_exports__);
     65 
     66 // EXPORTS
     67 __webpack_require__.d(__webpack_exports__, {
     68  NewTab: () => (/* binding */ NewTab),
     69  renderCache: () => (/* binding */ renderCache),
     70  renderWithoutState: () => (/* binding */ renderWithoutState)
     71 });
     72 
     73 ;// CONCATENATED MODULE: ./common/Actions.mjs
     74 /* This Source Code Form is subject to the terms of the Mozilla Public
     75 * License, v. 2.0. If a copy of the MPL was not distributed with this
     76 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     77 
     78 // This file is accessed from both content and system scopes.
     79 
     80 const MAIN_MESSAGE_TYPE = "ActivityStream:Main";
     81 const CONTENT_MESSAGE_TYPE = "ActivityStream:Content";
     82 const PRELOAD_MESSAGE_TYPE = "ActivityStream:PreloadedBrowser";
     83 const UI_CODE = 1;
     84 const BACKGROUND_PROCESS = 2;
     85 
     86 /**
     87 * globalImportContext - Are we in UI code (i.e. react, a dom) or some kind of background process?
     88 *                       Use this in action creators if you need different logic
     89 *                       for ui/background processes.
     90 */
     91 const globalImportContext =
     92  typeof Window === "undefined" ? BACKGROUND_PROCESS : UI_CODE;
     93 
     94 // Create an object that avoids accidental differing key/value pairs:
     95 // {
     96 //   INIT: "INIT",
     97 //   UNINIT: "UNINIT"
     98 // }
     99 const actionTypes = {};
    100 
    101 for (const type of [
    102  "ABOUT_SPONSORED_TOP_SITES",
    103  "ADDONS_INFO_REQUEST",
    104  "ADDONS_INFO_RESPONSE",
    105  "ADS_FEED_UPDATE",
    106  "ADS_INIT",
    107  "ADS_RESET",
    108  "ADS_UPDATE_SPOCS",
    109  "ADS_UPDATE_TILES",
    110  "BLOCK_SECTION",
    111  "BLOCK_URL",
    112  "BOOKMARK_URL",
    113  "CARD_SECTION_IMPRESSION",
    114  "CLEAR_PREF",
    115  "COPY_DOWNLOAD_LINK",
    116  "DELETE_BOOKMARK_BY_ID",
    117  "DELETE_HISTORY_URL",
    118  "DIALOG_CANCEL",
    119  "DIALOG_CLOSE",
    120  "DIALOG_OPEN",
    121  "DISABLE_SEARCH",
    122  "DISCOVERY_STREAM_CONFIG_CHANGE",
    123  "DISCOVERY_STREAM_CONFIG_RESET",
    124  "DISCOVERY_STREAM_CONFIG_RESET_DEFAULTS",
    125  "DISCOVERY_STREAM_CONFIG_SETUP",
    126  "DISCOVERY_STREAM_CONFIG_SET_VALUE",
    127  "DISCOVERY_STREAM_DEV_BLOCKS",
    128  "DISCOVERY_STREAM_DEV_BLOCKS_RESET",
    129  "DISCOVERY_STREAM_DEV_EXPIRE_CACHE",
    130  "DISCOVERY_STREAM_DEV_IDLE_DAILY",
    131  "DISCOVERY_STREAM_DEV_IMPRESSIONS",
    132  "DISCOVERY_STREAM_DEV_SHOW_PLACEHOLDER",
    133  "DISCOVERY_STREAM_DEV_SYNC_RS",
    134  "DISCOVERY_STREAM_DEV_SYSTEM_TICK",
    135  "DISCOVERY_STREAM_EXPERIMENT_DATA",
    136  "DISCOVERY_STREAM_FEEDS_UPDATE",
    137  "DISCOVERY_STREAM_FEED_UPDATE",
    138  "DISCOVERY_STREAM_IMPRESSION_STATS",
    139  "DISCOVERY_STREAM_LAYOUT_RESET",
    140  "DISCOVERY_STREAM_LAYOUT_UPDATE",
    141  "DISCOVERY_STREAM_LINK_BLOCKED",
    142  "DISCOVERY_STREAM_LOADED_CONTENT",
    143  "DISCOVERY_STREAM_PERSONALIZATION_INIT",
    144  "DISCOVERY_STREAM_PERSONALIZATION_LAST_UPDATED",
    145  "DISCOVERY_STREAM_PERSONALIZATION_OVERRIDE",
    146  "DISCOVERY_STREAM_PERSONALIZATION_RESET",
    147  "DISCOVERY_STREAM_PERSONALIZATION_TOGGLE",
    148  "DISCOVERY_STREAM_PERSONALIZATION_UPDATED",
    149  "DISCOVERY_STREAM_PREFS_SETUP",
    150  "DISCOVERY_STREAM_RETRY_FEED",
    151  "DISCOVERY_STREAM_SPOCS_CAPS",
    152  "DISCOVERY_STREAM_SPOCS_ENDPOINT",
    153  "DISCOVERY_STREAM_SPOCS_ONDEMAND_LOAD",
    154  "DISCOVERY_STREAM_SPOCS_ONDEMAND_RESET",
    155  "DISCOVERY_STREAM_SPOCS_ONDEMAND_UPDATE",
    156  "DISCOVERY_STREAM_SPOCS_PLACEMENTS",
    157  "DISCOVERY_STREAM_SPOCS_UPDATE",
    158  "DISCOVERY_STREAM_SPOC_BLOCKED",
    159  "DISCOVERY_STREAM_SPOC_IMPRESSION",
    160  "DISCOVERY_STREAM_SPOC_PLACEHOLDER_DURATION",
    161  "DISCOVERY_STREAM_TOPICS_LOADING",
    162  "DISCOVERY_STREAM_USER_EVENT",
    163  "DOWNLOAD_CHANGED",
    164  "FAKE_FOCUS_SEARCH",
    165  "FILL_SEARCH_TERM",
    166  "FOLLOW_SECTION",
    167  "HANDOFF_SEARCH_TO_AWESOMEBAR",
    168  "HIDE_PERSONALIZE",
    169  "HIDE_TOAST_MESSAGE",
    170  "INFERRED_PERSONALIZATION_MODEL_UPDATE",
    171  "INFERRED_PERSONALIZATION_REFRESH",
    172  "INFERRED_PERSONALIZATION_RESET",
    173  "INFERRED_PERSONALIZATION_UPDATE",
    174  "INIT",
    175  "INLINE_SELECTION_CLICK",
    176  "INLINE_SELECTION_IMPRESSION",
    177  "MESSAGE_BLOCK",
    178  "MESSAGE_CLICK",
    179  "MESSAGE_DISMISS",
    180  "MESSAGE_IMPRESSION",
    181  "MESSAGE_NOTIFY_VISIBILITY",
    182  "MESSAGE_SET",
    183  "MESSAGE_TOGGLE_VISIBILITY",
    184  "NEW_TAB_INIT",
    185  "NEW_TAB_INITIAL_STATE",
    186  "NEW_TAB_LOAD",
    187  "NEW_TAB_REHYDRATED",
    188  "NEW_TAB_STATE_REQUEST",
    189  "NEW_TAB_STATE_REQUEST_STARTUPCACHE",
    190  "NEW_TAB_STATE_REQUEST_WITHOUT_STARTUPCACHE",
    191  "NEW_TAB_UNLOAD",
    192  "OPEN_DOWNLOAD_FILE",
    193  "OPEN_LINK",
    194  "OPEN_NEW_WINDOW",
    195  "OPEN_PRIVATE_WINDOW",
    196  "OPEN_WEBEXT_SETTINGS",
    197  "PARTNER_LINK_ATTRIBUTION",
    198  "PLACES_BOOKMARKS_REMOVED",
    199  "PLACES_BOOKMARK_ADDED",
    200  "PLACES_HISTORY_CLEARED",
    201  "PLACES_LINKS_CHANGED",
    202  "PLACES_LINKS_DELETED",
    203  "PLACES_LINK_BLOCKED",
    204  "POCKET_CTA",
    205  "POCKET_WAITING_FOR_SPOC",
    206  "PREFS_INITIAL_VALUES",
    207  "PREF_CHANGED",
    208  "PREVIEW_REQUEST",
    209  "PREVIEW_REQUEST_CANCEL",
    210  "PREVIEW_RESPONSE",
    211  "PROMO_CARD_CLICK",
    212  "PROMO_CARD_DISMISS",
    213  "PROMO_CARD_IMPRESSION",
    214  "REFRESH_EXTERNAL_COMPONENTS",
    215  "REMOVE_DOWNLOAD_FILE",
    216  "REPORT_AD_OPEN",
    217  "REPORT_AD_SUBMIT",
    218  "REPORT_CLOSE",
    219  "REPORT_CONTENT_OPEN",
    220  "REPORT_CONTENT_SUBMIT",
    221  "RICH_ICON_MISSING",
    222  "SAVE_SESSION_PERF_DATA",
    223  "SCREENSHOT_UPDATED",
    224  "SECTION_DEREGISTER",
    225  "SECTION_DISABLE",
    226  "SECTION_ENABLE",
    227  "SECTION_OPTIONS_CHANGED",
    228  "SECTION_PERSONALIZATION_SET",
    229  "SECTION_PERSONALIZATION_UPDATE",
    230  "SECTION_REGISTER",
    231  "SECTION_UPDATE",
    232  "SECTION_UPDATE_CARD",
    233  "SETTINGS_CLOSE",
    234  "SETTINGS_OPEN",
    235  "SET_PREF",
    236  "SHOW_DOWNLOAD_FILE",
    237  "SHOW_FIREFOX_ACCOUNTS",
    238  "SHOW_PERSONALIZE",
    239  "SHOW_PRIVACY_INFO",
    240  "SHOW_SEARCH",
    241  "SHOW_TOAST_MESSAGE",
    242  "SKIPPED_SIGNIN",
    243  "SOV_UPDATED",
    244  "SUBMIT_EMAIL",
    245  "SUBMIT_SIGNIN",
    246  "SYSTEM_TICK",
    247  "TELEMETRY_IMPRESSION_STATS",
    248  "TELEMETRY_USER_EVENT",
    249  "TOPIC_SELECTION_IMPRESSION",
    250  "TOPIC_SELECTION_MAYBE_LATER",
    251  "TOPIC_SELECTION_SPOTLIGHT_CLOSE",
    252  "TOPIC_SELECTION_SPOTLIGHT_OPEN",
    253  "TOPIC_SELECTION_USER_DISMISS",
    254  "TOPIC_SELECTION_USER_OPEN",
    255  "TOPIC_SELECTION_USER_SAVE",
    256  "TOP_SITES_ADD",
    257  "TOP_SITES_CANCEL_EDIT",
    258  "TOP_SITES_CLOSE_SEARCH_SHORTCUTS_MODAL",
    259  "TOP_SITES_EDIT",
    260  "TOP_SITES_INSERT",
    261  "TOP_SITES_OPEN_SEARCH_SHORTCUTS_MODAL",
    262  "TOP_SITES_ORGANIC_IMPRESSION_STATS",
    263  "TOP_SITES_PIN",
    264  "TOP_SITES_PREFS_UPDATED",
    265  "TOP_SITES_SPONSORED_IMPRESSION_STATS",
    266  "TOP_SITES_UNPIN",
    267  "TOP_SITES_UPDATED",
    268  "TOTAL_BOOKMARKS_REQUEST",
    269  "TOTAL_BOOKMARKS_RESPONSE",
    270  "UNBLOCK_SECTION",
    271  "UNFOLLOW_SECTION",
    272  "UNINIT",
    273  "UPDATE_PINNED_SEARCH_SHORTCUTS",
    274  "UPDATE_SEARCH_SHORTCUTS",
    275  "WALLPAPERS_CATEGORY_SET",
    276  "WALLPAPERS_CUSTOM_SET",
    277  "WALLPAPERS_FEATURE_HIGHLIGHT_COUNTER_INCREMENT",
    278  "WALLPAPERS_FEATURE_HIGHLIGHT_CTA_CLICKED",
    279  "WALLPAPERS_FEATURE_HIGHLIGHT_DISMISSED",
    280  "WALLPAPERS_FEATURE_HIGHLIGHT_SEEN",
    281  "WALLPAPERS_SET",
    282  "WALLPAPER_CATEGORY_CLICK",
    283  "WALLPAPER_CLICK",
    284  "WALLPAPER_REMOVE_UPLOAD",
    285  "WALLPAPER_UPLOAD",
    286  "WEATHER_DETECT_LOCATION",
    287  "WEATHER_IMPRESSION",
    288  "WEATHER_LOAD_ERROR",
    289  "WEATHER_LOCATION_DATA_UPDATE",
    290  "WEATHER_LOCATION_SEARCH_UPDATE",
    291  "WEATHER_LOCATION_SUGGESTIONS_UPDATE",
    292  "WEATHER_OPEN_PROVIDER_URL",
    293  "WEATHER_OPT_IN_PROMPT_SELECTION",
    294  "WEATHER_QUERY_UPDATE",
    295  "WEATHER_SEARCH_ACTIVE",
    296  "WEATHER_UPDATE",
    297  "WEATHER_USER_OPT_IN_LOCATION",
    298  "WEBEXT_CLICK",
    299  "WEBEXT_DISMISS",
    300  "WIDGETS_LISTS_CHANGE_SELECTED",
    301  "WIDGETS_LISTS_SET",
    302  "WIDGETS_LISTS_SET_SELECTED",
    303  "WIDGETS_LISTS_UPDATE",
    304  "WIDGETS_LISTS_USER_EVENT",
    305  "WIDGETS_LISTS_USER_IMPRESSION",
    306  "WIDGETS_TIMER_END",
    307  "WIDGETS_TIMER_PAUSE",
    308  "WIDGETS_TIMER_PLAY",
    309  "WIDGETS_TIMER_RESET",
    310  "WIDGETS_TIMER_SET",
    311  "WIDGETS_TIMER_SET_DURATION",
    312  "WIDGETS_TIMER_SET_TYPE",
    313  "WIDGETS_TIMER_USER_EVENT",
    314  "WIDGETS_TIMER_USER_IMPRESSION",
    315 ]) {
    316  actionTypes[type] = type;
    317 }
    318 
    319 // Helper function for creating routed actions between content and main
    320 // Not intended to be used by consumers
    321 function _RouteMessage(action, options) {
    322  const meta = action.meta ? { ...action.meta } : {};
    323  if (!options || !options.from || !options.to) {
    324    throw new Error(
    325      "Routed Messages must have options as the second parameter, and must at least include a .from and .to property."
    326    );
    327  }
    328  // For each of these fields, if they are passed as an option,
    329  // add them to the action. If they are not defined, remove them.
    330  ["from", "to", "toTarget", "fromTarget", "skipMain", "skipLocal"].forEach(
    331    o => {
    332      if (typeof options[o] !== "undefined") {
    333        meta[o] = options[o];
    334      } else if (meta[o]) {
    335        delete meta[o];
    336      }
    337    }
    338  );
    339  return { ...action, meta };
    340 }
    341 
    342 /**
    343 * AlsoToMain - Creates a message that will be dispatched locally and also sent to the Main process.
    344 *
    345 * @param  {object} action Any redux action (required)
    346 * @param  {object} options
    347 * @param  {bool}   skipLocal Used by OnlyToMain to skip the main reducer
    348 * @param  {string} fromTarget The id of the content port from which the action originated. (optional)
    349 * @return {object} An action with added .meta properties
    350 */
    351 function AlsoToMain(action, fromTarget, skipLocal) {
    352  return _RouteMessage(action, {
    353    from: CONTENT_MESSAGE_TYPE,
    354    to: MAIN_MESSAGE_TYPE,
    355    fromTarget,
    356    skipLocal,
    357  });
    358 }
    359 
    360 /**
    361 * OnlyToMain - Creates a message that will be sent to the Main process and skip the local reducer.
    362 *
    363 * @param  {object} action Any redux action (required)
    364 * @param  {object} options
    365 * @param  {string} fromTarget The id of the content port from which the action originated. (optional)
    366 * @return {object} An action with added .meta properties
    367 */
    368 function OnlyToMain(action, fromTarget) {
    369  return AlsoToMain(action, fromTarget, true);
    370 }
    371 
    372 /**
    373 * BroadcastToContent - Creates a message that will be dispatched to main and sent to ALL content processes.
    374 *
    375 * @param  {object} action Any redux action (required)
    376 * @param  {object} options (optional)
    377 * @return {object} An action with added .meta properties
    378 */
    379 function BroadcastToContent(action, options) {
    380  return _RouteMessage(action, {
    381    from: MAIN_MESSAGE_TYPE,
    382    to: CONTENT_MESSAGE_TYPE,
    383    ...options,
    384  });
    385 }
    386 
    387 /**
    388 * AlsoToOneContent - Creates a message that will be will be dispatched to the main store
    389 *                    and also sent to a particular Content process.
    390 *
    391 * @param  {object} action Any redux action (required)
    392 * @param  {string} target The id of a content port
    393 * @param  {bool} skipMain Used by OnlyToOneContent to skip the main process
    394 * @return {object} An action with added .meta properties
    395 */
    396 function AlsoToOneContent(action, target, skipMain) {
    397  if (!target) {
    398    throw new Error(
    399      "You must provide a target ID as the second parameter of AlsoToOneContent. If you want to send to all content processes, use BroadcastToContent"
    400    );
    401  }
    402  return _RouteMessage(action, {
    403    from: MAIN_MESSAGE_TYPE,
    404    to: CONTENT_MESSAGE_TYPE,
    405    toTarget: target,
    406    skipMain,
    407  });
    408 }
    409 
    410 /**
    411 * OnlyToOneContent - Creates a message that will be sent to a particular Content process
    412 *                    and skip the main reducer.
    413 *
    414 * @param  {object} action Any redux action (required)
    415 * @param  {string} target The id of a content port
    416 * @return {object} An action with added .meta properties
    417 */
    418 function OnlyToOneContent(action, target) {
    419  return AlsoToOneContent(action, target, true);
    420 }
    421 
    422 /**
    423 * AlsoToPreloaded - Creates a message that dispatched to the main reducer and also sent to the preloaded tab.
    424 *
    425 * @param  {object} action Any redux action (required)
    426 * @return {object} An action with added .meta properties
    427 */
    428 function AlsoToPreloaded(action) {
    429  return _RouteMessage(action, {
    430    from: MAIN_MESSAGE_TYPE,
    431    to: PRELOAD_MESSAGE_TYPE,
    432  });
    433 }
    434 
    435 /**
    436 * UserEvent - A telemetry ping indicating a user action. This should only
    437 *                   be sent from the UI during a user session.
    438 *
    439 * @param  {object} data Fields to include in the ping (source, etc.)
    440 * @return {object} An AlsoToMain action
    441 */
    442 function UserEvent(data) {
    443  return AlsoToMain({
    444    type: actionTypes.TELEMETRY_USER_EVENT,
    445    data,
    446  });
    447 }
    448 
    449 /**
    450 * DiscoveryStreamUserEvent - A telemetry ping indicating a user action from Discovery Stream. This should only
    451 *                     be sent from the UI during a user session.
    452 *
    453 * @param  {object} data Fields to include in the ping (source, etc.)
    454 * @return {object} An AlsoToMain action
    455 */
    456 function DiscoveryStreamUserEvent(data) {
    457  return AlsoToMain({
    458    type: actionTypes.DISCOVERY_STREAM_USER_EVENT,
    459    data,
    460  });
    461 }
    462 
    463 /**
    464 * ImpressionStats - A telemetry ping indicating an impression stats.
    465 *
    466 * @param  {object} data Fields to include in the ping
    467 * @param  {int} importContext (For testing) Override the import context for testing.
    468 * #return {object} An action. For UI code, a AlsoToMain action.
    469 */
    470 function ImpressionStats(data, importContext = globalImportContext) {
    471  const action = {
    472    type: actionTypes.TELEMETRY_IMPRESSION_STATS,
    473    data,
    474  };
    475  return importContext === UI_CODE ? AlsoToMain(action) : action;
    476 }
    477 
    478 /**
    479 * DiscoveryStreamImpressionStats - A telemetry ping indicating an impression stats in Discovery Stream.
    480 *
    481 * @param  {object} data Fields to include in the ping
    482 * @param  {int} importContext (For testing) Override the import context for testing.
    483 * #return {object} An action. For UI code, a AlsoToMain action.
    484 */
    485 function DiscoveryStreamImpressionStats(
    486  data,
    487  importContext = globalImportContext
    488 ) {
    489  const action = {
    490    type: actionTypes.DISCOVERY_STREAM_IMPRESSION_STATS,
    491    data,
    492  };
    493  return importContext === UI_CODE ? AlsoToMain(action) : action;
    494 }
    495 
    496 /**
    497 * DiscoveryStreamLoadedContent - A telemetry ping indicating a content gets loaded in Discovery Stream.
    498 *
    499 * @param  {object} data Fields to include in the ping
    500 * @param  {int} importContext (For testing) Override the import context for testing.
    501 * #return {object} An action. For UI code, a AlsoToMain action.
    502 */
    503 function DiscoveryStreamLoadedContent(
    504  data,
    505  importContext = globalImportContext
    506 ) {
    507  const action = {
    508    type: actionTypes.DISCOVERY_STREAM_LOADED_CONTENT,
    509    data,
    510  };
    511  return importContext === UI_CODE ? AlsoToMain(action) : action;
    512 }
    513 
    514 function SetPref(prefName, value, importContext = globalImportContext) {
    515  const action = {
    516    type: actionTypes.SET_PREF,
    517    data: { name: prefName, value },
    518  };
    519  return importContext === UI_CODE ? AlsoToMain(action) : action;
    520 }
    521 
    522 function WebExtEvent(type, data, importContext = globalImportContext) {
    523  if (!data || !data.source) {
    524    throw new Error(
    525      'WebExtEvent actions should include a property "source", the id of the webextension that should receive the event.'
    526    );
    527  }
    528  const action = { type, data };
    529  return importContext === UI_CODE ? AlsoToMain(action) : action;
    530 }
    531 
    532 const actionCreators = {
    533  BroadcastToContent,
    534  UserEvent,
    535  DiscoveryStreamUserEvent,
    536  ImpressionStats,
    537  AlsoToOneContent,
    538  OnlyToOneContent,
    539  AlsoToMain,
    540  OnlyToMain,
    541  AlsoToPreloaded,
    542  SetPref,
    543  WebExtEvent,
    544  DiscoveryStreamImpressionStats,
    545  DiscoveryStreamLoadedContent,
    546 };
    547 
    548 // These are helpers to test for certain kinds of actions
    549 const actionUtils = {
    550  isSendToMain(action) {
    551    if (!action.meta) {
    552      return false;
    553    }
    554    return (
    555      action.meta.to === MAIN_MESSAGE_TYPE &&
    556      action.meta.from === CONTENT_MESSAGE_TYPE
    557    );
    558  },
    559  isBroadcastToContent(action) {
    560    if (!action.meta) {
    561      return false;
    562    }
    563    if (action.meta.to === CONTENT_MESSAGE_TYPE && !action.meta.toTarget) {
    564      return true;
    565    }
    566    return false;
    567  },
    568  isSendToOneContent(action) {
    569    if (!action.meta) {
    570      return false;
    571    }
    572    if (action.meta.to === CONTENT_MESSAGE_TYPE && action.meta.toTarget) {
    573      return true;
    574    }
    575    return false;
    576  },
    577  isSendToPreloaded(action) {
    578    if (!action.meta) {
    579      return false;
    580    }
    581    return (
    582      action.meta.to === PRELOAD_MESSAGE_TYPE &&
    583      action.meta.from === MAIN_MESSAGE_TYPE
    584    );
    585  },
    586  isFromMain(action) {
    587    if (!action.meta) {
    588      return false;
    589    }
    590    return (
    591      action.meta.from === MAIN_MESSAGE_TYPE &&
    592      action.meta.to === CONTENT_MESSAGE_TYPE
    593    );
    594  },
    595  getPortIdOfSender(action) {
    596    return (action.meta && action.meta.fromTarget) || null;
    597  },
    598  _RouteMessage,
    599 };
    600 
    601 ;// CONCATENATED MODULE: external "ReactRedux"
    602 const external_ReactRedux_namespaceObject = ReactRedux;
    603 ;// CONCATENATED MODULE: external "React"
    604 const external_React_namespaceObject = React;
    605 var external_React_default = /*#__PURE__*/__webpack_require__.n(external_React_namespaceObject);
    606 ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamAdmin/DiscoveryStreamAdmin.jsx
    607 function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
    608 /* This Source Code Form is subject to the terms of the Mozilla Public
    609 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
    610 * You can obtain one at http://mozilla.org/MPL/2.0/. */
    611 
    612 
    613 
    614 
    615 
    616 // Pref Constants
    617 const PREF_AD_SIZE_MEDIUM_RECTANGLE = "newtabAdSize.mediumRectangle";
    618 const PREF_AD_SIZE_BILLBOARD = "newtabAdSize.billboard";
    619 const PREF_AD_SIZE_LEADERBOARD = "newtabAdSize.leaderboard";
    620 const PREF_SECTIONS_ENABLED = "discoverystream.sections.enabled";
    621 const PREF_SPOC_PLACEMENTS = "discoverystream.placements.spocs";
    622 const PREF_SPOC_COUNTS = "discoverystream.placements.spocs.counts";
    623 const PREF_CONTEXTUAL_ADS_ENABLED = "discoverystream.sections.contextualAds.enabled";
    624 const PREF_CONTEXTUAL_BANNER_PLACEMENTS = "discoverystream.placements.contextualBanners";
    625 const PREF_CONTEXTUAL_BANNER_COUNTS = "discoverystream.placements.contextualBanners.counts";
    626 const PREF_UNIFIED_ADS_ENABLED = "unifiedAds.spocs.enabled";
    627 const PREF_UNIFIED_ADS_ENDPOINT = "unifiedAds.endpoint";
    628 const PREF_ALLOWED_ENDPOINTS = "discoverystream.endpoints";
    629 const PREF_OHTTP_CONFIG = "discoverystream.ohttp.configURL";
    630 const PREF_OHTTP_RELAY = "discoverystream.ohttp.relayURL";
    631 const Row = props => /*#__PURE__*/external_React_default().createElement("tr", _extends({
    632  className: "message-item"
    633 }, props), props.children);
    634 function relativeTime(timestamp) {
    635  if (!timestamp) {
    636    return "";
    637  }
    638  const seconds = Math.floor((Date.now() - timestamp) / 1000);
    639  const minutes = Math.floor((Date.now() - timestamp) / 60000);
    640  if (seconds < 2) {
    641    return "just now";
    642  } else if (seconds < 60) {
    643    return `${seconds} seconds ago`;
    644  } else if (minutes === 1) {
    645    return "1 minute ago";
    646  } else if (minutes < 600) {
    647    return `${minutes} minutes ago`;
    648  }
    649  return new Date(timestamp).toLocaleString();
    650 }
    651 class ToggleStoryButton extends (external_React_default()).PureComponent {
    652  constructor(props) {
    653    super(props);
    654    this.handleClick = this.handleClick.bind(this);
    655  }
    656  handleClick() {
    657    this.props.onClick(this.props.story);
    658  }
    659  render() {
    660    return /*#__PURE__*/external_React_default().createElement("button", {
    661      onClick: this.handleClick
    662    }, "collapse/open");
    663  }
    664 }
    665 class TogglePrefCheckbox extends (external_React_default()).PureComponent {
    666  constructor(props) {
    667    super(props);
    668    this.onChange = this.onChange.bind(this);
    669  }
    670  onChange(event) {
    671    this.props.onChange(this.props.pref, event.target.checked);
    672  }
    673  render() {
    674    return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement("input", {
    675      type: "checkbox",
    676      checked: this.props.checked,
    677      onChange: this.onChange,
    678      disabled: this.props.disabled
    679    }), " ", this.props.pref, " ");
    680  }
    681 }
    682 class Personalization extends (external_React_default()).PureComponent {
    683  constructor(props) {
    684    super(props);
    685    this.togglePersonalization = this.togglePersonalization.bind(this);
    686  }
    687  togglePersonalization() {
    688    this.props.dispatch(actionCreators.OnlyToMain({
    689      type: actionTypes.DISCOVERY_STREAM_PERSONALIZATION_TOGGLE
    690    }));
    691  }
    692  render() {
    693    const {
    694      lastUpdated,
    695      initialized
    696    } = this.props.state.Personalization;
    697    return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement("table", null, /*#__PURE__*/external_React_default().createElement("tbody", null, /*#__PURE__*/external_React_default().createElement(Row, null, /*#__PURE__*/external_React_default().createElement("td", {
    698      colSpan: "2"
    699    }, /*#__PURE__*/external_React_default().createElement(TogglePrefCheckbox, {
    700      checked: this.props.personalized,
    701      pref: "personalized",
    702      onChange: this.togglePersonalization
    703    }))), /*#__PURE__*/external_React_default().createElement(Row, null, /*#__PURE__*/external_React_default().createElement("td", {
    704      className: "min"
    705    }, "Personalization Last Updated"), /*#__PURE__*/external_React_default().createElement("td", null, relativeTime(lastUpdated) || "(no data)")), /*#__PURE__*/external_React_default().createElement(Row, null, /*#__PURE__*/external_React_default().createElement("td", {
    706      className: "min"
    707    }, "Personalization Initialized"), /*#__PURE__*/external_React_default().createElement("td", null, initialized ? "true" : "false")))));
    708  }
    709 }
    710 class DiscoveryStreamAdminUI extends (external_React_default()).PureComponent {
    711  constructor(props) {
    712    super(props);
    713    this.restorePrefDefaults = this.restorePrefDefaults.bind(this);
    714    this.setConfigValue = this.setConfigValue.bind(this);
    715    this.expireCache = this.expireCache.bind(this);
    716    this.refreshCache = this.refreshCache.bind(this);
    717    this.showPlaceholder = this.showPlaceholder.bind(this);
    718    this.idleDaily = this.idleDaily.bind(this);
    719    this.systemTick = this.systemTick.bind(this);
    720    this.syncRemoteSettings = this.syncRemoteSettings.bind(this);
    721    this.onStoryToggle = this.onStoryToggle.bind(this);
    722    this.handleWeatherSubmit = this.handleWeatherSubmit.bind(this);
    723    this.handleWeatherUpdate = this.handleWeatherUpdate.bind(this);
    724    this.resetBlocks = this.resetBlocks.bind(this);
    725    this.refreshInferredPersonalization = this.refreshInferredPersonalization.bind(this);
    726    this.refreshTopicSelectionCache = this.refreshTopicSelectionCache.bind(this);
    727    this.handleSectionsToggle = this.handleSectionsToggle.bind(this);
    728    this.toggleIABBanners = this.toggleIABBanners.bind(this);
    729    this.handleAllizomToggle = this.handleAllizomToggle.bind(this);
    730    this.sendConversionEvent = this.sendConversionEvent.bind(this);
    731    this.state = {
    732      toggledStories: {},
    733      weatherQuery: ""
    734    };
    735  }
    736  setConfigValue(configName, configValue) {
    737    this.props.dispatch(actionCreators.OnlyToMain({
    738      type: actionTypes.DISCOVERY_STREAM_CONFIG_SET_VALUE,
    739      data: {
    740        name: configName,
    741        value: configValue
    742      }
    743    }));
    744  }
    745  restorePrefDefaults() {
    746    this.props.dispatch(actionCreators.OnlyToMain({
    747      type: actionTypes.DISCOVERY_STREAM_CONFIG_RESET_DEFAULTS
    748    }));
    749  }
    750  refreshCache() {
    751    const {
    752      config
    753    } = this.props.state.DiscoveryStream;
    754    this.props.dispatch(actionCreators.OnlyToMain({
    755      type: actionTypes.DISCOVERY_STREAM_CONFIG_CHANGE,
    756      data: config
    757    }));
    758  }
    759  refreshInferredPersonalization() {
    760    this.props.dispatch(actionCreators.OnlyToMain({
    761      type: actionTypes.INFERRED_PERSONALIZATION_REFRESH
    762    }));
    763  }
    764  refreshTopicSelectionCache() {
    765    this.props.dispatch(actionCreators.SetPref("discoverystream.topicSelection.onboarding.displayCount", 0));
    766    this.props.dispatch(actionCreators.SetPref("discoverystream.topicSelection.onboarding.maybeDisplay", true));
    767  }
    768  dispatchSimpleAction(type) {
    769    this.props.dispatch(actionCreators.OnlyToMain({
    770      type
    771    }));
    772  }
    773  resetBlocks() {
    774    this.props.dispatch(actionCreators.OnlyToMain({
    775      type: actionTypes.DISCOVERY_STREAM_DEV_BLOCKS_RESET
    776    }));
    777  }
    778  systemTick() {
    779    this.dispatchSimpleAction(actionTypes.DISCOVERY_STREAM_DEV_SYSTEM_TICK);
    780  }
    781  expireCache() {
    782    this.dispatchSimpleAction(actionTypes.DISCOVERY_STREAM_DEV_EXPIRE_CACHE);
    783  }
    784  showPlaceholder() {
    785    this.dispatchSimpleAction(actionTypes.DISCOVERY_STREAM_DEV_SHOW_PLACEHOLDER);
    786  }
    787  idleDaily() {
    788    this.dispatchSimpleAction(actionTypes.DISCOVERY_STREAM_DEV_IDLE_DAILY);
    789  }
    790  syncRemoteSettings() {
    791    this.dispatchSimpleAction(actionTypes.DISCOVERY_STREAM_DEV_SYNC_RS);
    792  }
    793  handleWeatherUpdate(e) {
    794    this.setState({
    795      weatherQuery: e.target.value || ""
    796    });
    797  }
    798  handleWeatherSubmit(e) {
    799    e.preventDefault();
    800    const {
    801      weatherQuery
    802    } = this.state;
    803    this.props.dispatch(actionCreators.SetPref("weather.query", weatherQuery));
    804  }
    805  toggleIABBanners(e) {
    806    const {
    807      pressed,
    808      id
    809    } = e.target;
    810 
    811    // Set the active pref to true/false
    812    switch (id) {
    813      case "newtab_billboard":
    814        // Update boolean pref for billboard ad size
    815        this.props.dispatch(actionCreators.SetPref(PREF_AD_SIZE_BILLBOARD, pressed));
    816        break;
    817      case "newtab_leaderboard":
    818        // Update boolean pref for billboard ad size
    819        this.props.dispatch(actionCreators.SetPref(PREF_AD_SIZE_LEADERBOARD, pressed));
    820        break;
    821      case "newtab_rectangle":
    822        // Update boolean pref for mediumRectangle (MREC) ad size
    823        this.props.dispatch(actionCreators.SetPref(PREF_AD_SIZE_MEDIUM_RECTANGLE, pressed));
    824        break;
    825    }
    826 
    827    // Note: The counts array is passively updated whenever the placements array is updated.
    828    // The default pref values for each are:
    829    // PREF_SPOC_PLACEMENTS: "newtab_spocs"
    830    // PREF_SPOC_COUNTS: "6"
    831    const generateSpocPrefValues = () => {
    832      const placements = this.props.otherPrefs[PREF_SPOC_PLACEMENTS]?.split(",").map(item => item.trim()).filter(item => item) || [];
    833      const counts = this.props.otherPrefs[PREF_SPOC_COUNTS]?.split(",").map(item => item.trim()).filter(item => item) || [];
    834 
    835      // Confirm that the IAB type will have a count value of "1"
    836      const supportIABAdTypes = ["newtab_leaderboard", "newtab_rectangle", "newtab_billboard"];
    837      let countValue;
    838      if (supportIABAdTypes.includes(id)) {
    839        countValue = "1"; // Default count value for all IAB ad types
    840      } else {
    841        throw new Error("IAB ad type not supported");
    842      }
    843      if (pressed) {
    844        // If pressed is true, add the id to the placements array
    845        if (!placements.includes(id)) {
    846          placements.push(id);
    847          counts.push(countValue);
    848        }
    849      } else {
    850        // If pressed is false, remove the id from the placements array
    851        const index = placements.indexOf(id);
    852        if (index !== -1) {
    853          placements.splice(index, 1);
    854          counts.splice(index, 1);
    855        }
    856      }
    857      return {
    858        placements: placements.join(", "),
    859        counts: counts.join(", ")
    860      };
    861    };
    862    const {
    863      placements,
    864      counts
    865    } = generateSpocPrefValues();
    866 
    867    // Update prefs with new values
    868    this.props.dispatch(actionCreators.SetPref(PREF_SPOC_PLACEMENTS, placements));
    869    this.props.dispatch(actionCreators.SetPref(PREF_SPOC_COUNTS, counts));
    870 
    871    // If contextual ads, sections, and one of the banners are enabled
    872    // update the contextualBanner prefs to include the banner value and count
    873    // Else, clear the prefs
    874    if (PREF_CONTEXTUAL_ADS_ENABLED && PREF_SECTIONS_ENABLED) {
    875      if (PREF_AD_SIZE_BILLBOARD && placements.includes("newtab_billboard")) {
    876        this.props.dispatch(actionCreators.SetPref(PREF_CONTEXTUAL_BANNER_PLACEMENTS, "newtab_billboard"));
    877        this.props.dispatch(actionCreators.SetPref(PREF_CONTEXTUAL_BANNER_COUNTS, "1"));
    878      } else if (PREF_AD_SIZE_LEADERBOARD && placements.includes("newtab_leaderboard")) {
    879        this.props.dispatch(actionCreators.SetPref(PREF_CONTEXTUAL_BANNER_PLACEMENTS, "newtab_leaderboard"));
    880        this.props.dispatch(actionCreators.SetPref(PREF_CONTEXTUAL_BANNER_COUNTS, "1"));
    881      } else {
    882        this.props.dispatch(actionCreators.SetPref(PREF_CONTEXTUAL_BANNER_PLACEMENTS, ""));
    883        this.props.dispatch(actionCreators.SetPref(PREF_CONTEXTUAL_BANNER_COUNTS, ""));
    884      }
    885    }
    886  }
    887  handleSectionsToggle(e) {
    888    const {
    889      pressed
    890    } = e.target;
    891    this.props.dispatch(actionCreators.SetPref(PREF_SECTIONS_ENABLED, pressed));
    892    this.props.dispatch(actionCreators.SetPref("discoverystream.sections.cards.enabled", pressed));
    893  }
    894  sendConversionEvent() {
    895    const detail = {
    896      partnerId: "295BEEF7-1E3B-4128-B8F8-858E12AA660B",
    897      lookbackDays: 7,
    898      impressionType: "default"
    899    };
    900    const event = new CustomEvent("FirefoxConversionNotification", {
    901      detail,
    902      bubbles: true,
    903      composed: true
    904    });
    905    window?.dispatchEvent(event);
    906  }
    907  renderComponent(width, component) {
    908    return /*#__PURE__*/external_React_default().createElement("table", null, /*#__PURE__*/external_React_default().createElement("tbody", null, /*#__PURE__*/external_React_default().createElement(Row, null, /*#__PURE__*/external_React_default().createElement("td", {
    909      className: "min"
    910    }, "Type"), /*#__PURE__*/external_React_default().createElement("td", null, component.type)), /*#__PURE__*/external_React_default().createElement(Row, null, /*#__PURE__*/external_React_default().createElement("td", {
    911      className: "min"
    912    }, "Width"), /*#__PURE__*/external_React_default().createElement("td", null, width)), component.feed && this.renderFeed(component.feed)));
    913  }
    914  renderWeatherData() {
    915    const {
    916      suggestions
    917    } = this.props.state.Weather;
    918    let weatherTable;
    919    if (suggestions) {
    920      weatherTable = /*#__PURE__*/external_React_default().createElement("div", {
    921        className: "weather-section"
    922      }, /*#__PURE__*/external_React_default().createElement("form", {
    923        onSubmit: this.handleWeatherSubmit
    924      }, /*#__PURE__*/external_React_default().createElement("label", {
    925        htmlFor: "weather-query"
    926      }, "Weather query"), /*#__PURE__*/external_React_default().createElement("input", {
    927        type: "text",
    928        min: "3",
    929        max: "10",
    930        id: "weather-query",
    931        onChange: this.handleWeatherUpdate,
    932        value: this.weatherQuery
    933      }), /*#__PURE__*/external_React_default().createElement("button", {
    934        type: "submit"
    935      }, "Submit")), /*#__PURE__*/external_React_default().createElement("table", null, /*#__PURE__*/external_React_default().createElement("tbody", null, suggestions.map(suggestion => /*#__PURE__*/external_React_default().createElement("tr", {
    936        className: "message-item",
    937        key: suggestion.city_name
    938      }, /*#__PURE__*/external_React_default().createElement("td", {
    939        className: "message-id"
    940      }, /*#__PURE__*/external_React_default().createElement("span", null, suggestion.city_name, " ", /*#__PURE__*/external_React_default().createElement("br", null))), /*#__PURE__*/external_React_default().createElement("td", {
    941        className: "message-summary"
    942      }, /*#__PURE__*/external_React_default().createElement("pre", null, JSON.stringify(suggestion, null, 2))))))));
    943    }
    944    return weatherTable;
    945  }
    946  renderPersonalizationData() {
    947    const {
    948      inferredInterests,
    949      coarseInferredInterests,
    950      coarsePrivateInferredInterests
    951    } = this.props.state.InferredPersonalization;
    952    return /*#__PURE__*/external_React_default().createElement("div", null, " ", "Inferred Interests:", /*#__PURE__*/external_React_default().createElement("pre", null, JSON.stringify(inferredInterests, null, 2)), " Coarse Inferred Interests:", /*#__PURE__*/external_React_default().createElement("pre", null, JSON.stringify(coarseInferredInterests, null, 2)), " Coarse Inferred Interests With Differential Privacy:", /*#__PURE__*/external_React_default().createElement("pre", null, JSON.stringify(coarsePrivateInferredInterests, null, 2)));
    953  }
    954  renderFeedData(url) {
    955    const {
    956      feeds
    957    } = this.props.state.DiscoveryStream;
    958    const feed = feeds.data[url].data;
    959    return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement("h4", null, "Feed url: ", url), /*#__PURE__*/external_React_default().createElement("table", null, /*#__PURE__*/external_React_default().createElement("tbody", null, feed.recommendations?.map(story => this.renderStoryData(story)))));
    960  }
    961  renderFeedsData() {
    962    const {
    963      feeds
    964    } = this.props.state.DiscoveryStream;
    965    return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, Object.keys(feeds.data).map(url => this.renderFeedData(url)));
    966  }
    967  renderImpressionsData() {
    968    const {
    969      impressions
    970    } = this.props.state.DiscoveryStream;
    971    return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement("h4", null, "Feed Impressions"), /*#__PURE__*/external_React_default().createElement("table", null, /*#__PURE__*/external_React_default().createElement("tbody", null, Object.keys(impressions.feed).map(key => {
    972      return /*#__PURE__*/external_React_default().createElement(Row, {
    973        key: key
    974      }, /*#__PURE__*/external_React_default().createElement("td", {
    975        className: "min"
    976      }, key), /*#__PURE__*/external_React_default().createElement("td", null, relativeTime(impressions.feed[key]) || "(no data)"));
    977    }))));
    978  }
    979  renderBlocksData() {
    980    const {
    981      blocks
    982    } = this.props.state.DiscoveryStream;
    983    return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement("h4", null, "Blocks"), /*#__PURE__*/external_React_default().createElement("button", {
    984      className: "button",
    985      onClick: this.resetBlocks
    986    }, "Reset Blocks"), " ", /*#__PURE__*/external_React_default().createElement("table", null, /*#__PURE__*/external_React_default().createElement("tbody", null, Object.keys(blocks).map(key => {
    987      return /*#__PURE__*/external_React_default().createElement(Row, {
    988        key: key
    989      }, /*#__PURE__*/external_React_default().createElement("td", {
    990        className: "min"
    991      }, key));
    992    }))));
    993  }
    994  handleAllizomToggle(e) {
    995    const prefs = this.props.otherPrefs;
    996    const unifiedAdsSpocsEnabled = prefs[PREF_UNIFIED_ADS_ENABLED];
    997    if (!unifiedAdsSpocsEnabled) {
    998      return;
    999    }
   1000    const {
   1001      pressed
   1002    } = e.target;
   1003    const {
   1004      dispatch
   1005    } = this.props;
   1006    const allowedEndpoints = prefs[PREF_ALLOWED_ENDPOINTS];
   1007    const setPref = (pref = "", value = "") => {
   1008      dispatch(actionCreators.SetPref(pref, value));
   1009    };
   1010    const clearPref = (pref = "") => {
   1011      dispatch(actionCreators.OnlyToMain({
   1012        type: actionTypes.CLEAR_PREF,
   1013        data: {
   1014          name: pref
   1015        }
   1016      }));
   1017    };
   1018    if (pressed) {
   1019      setPref(PREF_UNIFIED_ADS_ENDPOINT, "https://ads.allizom.org/");
   1020      setPref(PREF_ALLOWED_ENDPOINTS, `${allowedEndpoints},https://ads.allizom.org/`);
   1021      setPref(PREF_OHTTP_CONFIG, "https://stage.ohttp-gateway.nonprod.webservices.mozgcp.net/ohttp-configs");
   1022      setPref(PREF_OHTTP_RELAY, "https://mozilla-ohttp-relay-test.edgecompute.app/");
   1023    } else {
   1024      clearPref(PREF_UNIFIED_ADS_ENDPOINT);
   1025      clearPref(PREF_ALLOWED_ENDPOINTS);
   1026      clearPref(PREF_OHTTP_CONFIG);
   1027      clearPref(PREF_OHTTP_RELAY);
   1028    }
   1029  }
   1030  renderSpocs() {
   1031    const {
   1032      spocs
   1033    } = this.props.state.DiscoveryStream;
   1034    const unifiedAdsSpocsEnabled = this.props.otherPrefs[PREF_UNIFIED_ADS_ENABLED];
   1035 
   1036    // Determine which mechanism is querying the UAPI ads server
   1037    const PREF_UNIFIED_ADS_ADSFEED_ENABLED = "unifiedAds.adsFeed.enabled";
   1038    const adsFeedEnabled = this.props.otherPrefs[PREF_UNIFIED_ADS_ADSFEED_ENABLED];
   1039    const unifiedAdsEndpoint = this.props.otherPrefs[PREF_UNIFIED_ADS_ENDPOINT];
   1040    const spocsEndpoint = unifiedAdsSpocsEnabled ? unifiedAdsEndpoint : spocs.spocs_endpoint;
   1041    let spocsData = [];
   1042    let allizomEnabled = spocsEndpoint?.includes("allizom");
   1043    if (spocs.data && spocs.data.newtab_spocs && spocs.data.newtab_spocs.items) {
   1044      spocsData = spocs.data.newtab_spocs.items || [];
   1045    }
   1046    return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement("table", null, /*#__PURE__*/external_React_default().createElement("tbody", null, /*#__PURE__*/external_React_default().createElement(Row, null, /*#__PURE__*/external_React_default().createElement("td", {
   1047      colSpan: "2"
   1048    }, /*#__PURE__*/external_React_default().createElement("moz-toggle", {
   1049      id: "sections-toggle",
   1050      disabled: !unifiedAdsSpocsEnabled || null,
   1051      pressed: allizomEnabled || null,
   1052      onToggle: this.handleAllizomToggle,
   1053      label: "Toggle allizom"
   1054    }))), /*#__PURE__*/external_React_default().createElement(Row, null, /*#__PURE__*/external_React_default().createElement("td", {
   1055      className: "min"
   1056    }, "adsfeed enabled"), /*#__PURE__*/external_React_default().createElement("td", null, adsFeedEnabled ? "true" : "false")), /*#__PURE__*/external_React_default().createElement(Row, null, /*#__PURE__*/external_React_default().createElement("td", {
   1057      className: "min"
   1058    }, "spocs endpoint"), /*#__PURE__*/external_React_default().createElement("td", null, spocsEndpoint)), /*#__PURE__*/external_React_default().createElement(Row, null, /*#__PURE__*/external_React_default().createElement("td", {
   1059      className: "min"
   1060    }, "Data last fetched"), /*#__PURE__*/external_React_default().createElement("td", null, relativeTime(spocs.lastUpdated))))), /*#__PURE__*/external_React_default().createElement("h4", null, "Spoc data"), /*#__PURE__*/external_React_default().createElement("table", null, /*#__PURE__*/external_React_default().createElement("tbody", null, spocsData.map(spoc => this.renderStoryData(spoc)))), /*#__PURE__*/external_React_default().createElement("h4", null, "Spoc frequency caps"), /*#__PURE__*/external_React_default().createElement("table", null, /*#__PURE__*/external_React_default().createElement("tbody", null, spocs.frequency_caps.map(spoc => this.renderStoryData(spoc)))));
   1061  }
   1062  onStoryToggle(story) {
   1063    const {
   1064      toggledStories
   1065    } = this.state;
   1066    this.setState({
   1067      toggledStories: {
   1068        ...toggledStories,
   1069        [story.id]: !toggledStories[story.id]
   1070      }
   1071    });
   1072  }
   1073  renderStoryData(story) {
   1074    let storyData = "";
   1075    if (this.state.toggledStories[story.id]) {
   1076      storyData = JSON.stringify(story, null, 2);
   1077    }
   1078    return /*#__PURE__*/external_React_default().createElement("tr", {
   1079      className: "message-item",
   1080      key: story.id
   1081    }, /*#__PURE__*/external_React_default().createElement("td", {
   1082      className: "message-id"
   1083    }, /*#__PURE__*/external_React_default().createElement("span", null, story.id, " ", /*#__PURE__*/external_React_default().createElement("br", null)), /*#__PURE__*/external_React_default().createElement(ToggleStoryButton, {
   1084      story: story,
   1085      onClick: this.onStoryToggle
   1086    })), /*#__PURE__*/external_React_default().createElement("td", {
   1087      className: "message-summary"
   1088    }, /*#__PURE__*/external_React_default().createElement("pre", null, storyData)));
   1089  }
   1090  renderFeed(feed) {
   1091    const {
   1092      feeds
   1093    } = this.props.state.DiscoveryStream;
   1094    if (!feed.url) {
   1095      return null;
   1096    }
   1097    return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement(Row, null, /*#__PURE__*/external_React_default().createElement("td", {
   1098      className: "min"
   1099    }, "Feed url"), /*#__PURE__*/external_React_default().createElement("td", null, feed.url)), /*#__PURE__*/external_React_default().createElement(Row, null, /*#__PURE__*/external_React_default().createElement("td", {
   1100      className: "min"
   1101    }, "Data last fetched"), /*#__PURE__*/external_React_default().createElement("td", null, relativeTime(feeds.data[feed.url] ? feeds.data[feed.url].lastUpdated : null) || "(no data)")));
   1102  }
   1103  render() {
   1104    const prefToggles = "enabled collapsible".split(" ");
   1105    const {
   1106      config,
   1107      layout
   1108    } = this.props.state.DiscoveryStream;
   1109    const personalized = this.props.otherPrefs["discoverystream.personalization.enabled"];
   1110    const sectionsEnabled = this.props.otherPrefs[PREF_SECTIONS_ENABLED];
   1111 
   1112    // Prefs for IAB Banners
   1113    const mediumRectangleEnabled = this.props.otherPrefs[PREF_AD_SIZE_MEDIUM_RECTANGLE];
   1114    const billboardsEnabled = this.props.otherPrefs[PREF_AD_SIZE_BILLBOARD];
   1115    const leaderboardEnabled = this.props.otherPrefs[PREF_AD_SIZE_LEADERBOARD];
   1116    const spocPlacements = this.props.otherPrefs[PREF_SPOC_PLACEMENTS];
   1117    const mediumRectangleEnabledPressed = mediumRectangleEnabled && spocPlacements.includes("newtab_rectangle");
   1118    const billboardPressed = billboardsEnabled && spocPlacements.includes("newtab_billboard");
   1119    const leaderboardPressed = leaderboardEnabled && spocPlacements.includes("newtab_leaderboard");
   1120    return /*#__PURE__*/external_React_default().createElement("div", null, /*#__PURE__*/external_React_default().createElement("button", {
   1121      className: "button",
   1122      onClick: this.restorePrefDefaults
   1123    }, "Restore Pref Defaults"), " ", /*#__PURE__*/external_React_default().createElement("button", {
   1124      className: "button",
   1125      onClick: this.refreshCache
   1126    }, "Refresh Cache"), /*#__PURE__*/external_React_default().createElement("br", null), /*#__PURE__*/external_React_default().createElement("button", {
   1127      className: "button",
   1128      onClick: this.expireCache
   1129    }, "Expire Cache"), " ", /*#__PURE__*/external_React_default().createElement("button", {
   1130      className: "button",
   1131      onClick: this.systemTick
   1132    }, "Trigger System Tick"), " ", /*#__PURE__*/external_React_default().createElement("button", {
   1133      className: "button",
   1134      onClick: this.idleDaily
   1135    }, "Trigger Idle Daily"), /*#__PURE__*/external_React_default().createElement("br", null), /*#__PURE__*/external_React_default().createElement("button", {
   1136      className: "button",
   1137      onClick: this.refreshInferredPersonalization
   1138    }, "Refresh Inferred Personalization"), /*#__PURE__*/external_React_default().createElement("br", null), /*#__PURE__*/external_React_default().createElement("button", {
   1139      className: "button",
   1140      onClick: this.syncRemoteSettings
   1141    }, "Sync Remote Settings"), " ", /*#__PURE__*/external_React_default().createElement("button", {
   1142      className: "button",
   1143      onClick: this.refreshTopicSelectionCache
   1144    }, "Refresh Topic selection count"), /*#__PURE__*/external_React_default().createElement("br", null), /*#__PURE__*/external_React_default().createElement("button", {
   1145      className: "button",
   1146      onClick: this.showPlaceholder
   1147    }, "Show Placeholder Cards"), " ", /*#__PURE__*/external_React_default().createElement("div", {
   1148      className: "toggle-wrapper"
   1149    }, /*#__PURE__*/external_React_default().createElement("moz-toggle", {
   1150      id: "sections-toggle",
   1151      pressed: sectionsEnabled || null,
   1152      onToggle: this.handleSectionsToggle,
   1153      label: "Toggle DS Sections"
   1154    })), /*#__PURE__*/external_React_default().createElement("details", {
   1155      className: "details-section"
   1156    }, /*#__PURE__*/external_React_default().createElement("summary", null, "IAB Banner Ad Sizes"), /*#__PURE__*/external_React_default().createElement("div", {
   1157      className: "toggle-wrapper"
   1158    }, /*#__PURE__*/external_React_default().createElement("moz-toggle", {
   1159      id: "newtab_leaderboard",
   1160      pressed: leaderboardPressed || null,
   1161      onToggle: this.toggleIABBanners,
   1162      label: "Enable IAB Leaderboard"
   1163    })), /*#__PURE__*/external_React_default().createElement("div", {
   1164      className: "toggle-wrapper"
   1165    }, /*#__PURE__*/external_React_default().createElement("moz-toggle", {
   1166      id: "newtab_billboard",
   1167      pressed: billboardPressed || null,
   1168      onToggle: this.toggleIABBanners,
   1169      label: "Enable IAB Billboard"
   1170    })), /*#__PURE__*/external_React_default().createElement("div", {
   1171      className: "toggle-wrapper"
   1172    }, /*#__PURE__*/external_React_default().createElement("moz-toggle", {
   1173      id: "newtab_rectangle",
   1174      pressed: mediumRectangleEnabledPressed || null,
   1175      onToggle: this.toggleIABBanners,
   1176      label: "Enable IAB Medium Rectangle (MREC)"
   1177    }))), /*#__PURE__*/external_React_default().createElement("button", {
   1178      className: "button",
   1179      onClick: this.sendConversionEvent
   1180    }, "Send conversion event"), /*#__PURE__*/external_React_default().createElement("table", null, /*#__PURE__*/external_React_default().createElement("tbody", null, prefToggles.map(pref => /*#__PURE__*/external_React_default().createElement(Row, {
   1181      key: pref
   1182    }, /*#__PURE__*/external_React_default().createElement("td", null, /*#__PURE__*/external_React_default().createElement(TogglePrefCheckbox, {
   1183      checked: config[pref],
   1184      pref: pref,
   1185      onChange: this.setConfigValue
   1186    })))))), /*#__PURE__*/external_React_default().createElement("h3", null, "Layout"), layout.map((row, rowIndex) => /*#__PURE__*/external_React_default().createElement("div", {
   1187      key: `row-${rowIndex}`
   1188    }, row.components.map((component, componentIndex) => /*#__PURE__*/external_React_default().createElement("div", {
   1189      key: `component-${componentIndex}`,
   1190      className: "ds-component"
   1191    }, this.renderComponent(row.width, component))))), /*#__PURE__*/external_React_default().createElement("h3", null, "Personalization"), /*#__PURE__*/external_React_default().createElement(Personalization, {
   1192      personalized: personalized,
   1193      dispatch: this.props.dispatch,
   1194      state: {
   1195        Personalization: this.props.state.Personalization
   1196      }
   1197    }), /*#__PURE__*/external_React_default().createElement("h3", null, "Spocs"), this.renderSpocs(), /*#__PURE__*/external_React_default().createElement("h3", null, "Feeds Data"), /*#__PURE__*/external_React_default().createElement("div", {
   1198      className: "large-data-container"
   1199    }, this.renderFeedsData()), /*#__PURE__*/external_React_default().createElement("h3", null, "Impressions Data"), /*#__PURE__*/external_React_default().createElement("div", {
   1200      className: "large-data-container"
   1201    }, this.renderImpressionsData()), /*#__PURE__*/external_React_default().createElement("h3", null, "Blocked Data"), /*#__PURE__*/external_React_default().createElement("div", {
   1202      className: "large-data-container"
   1203    }, this.renderBlocksData()), /*#__PURE__*/external_React_default().createElement("h3", null, "Weather Data"), this.renderWeatherData(), /*#__PURE__*/external_React_default().createElement("h3", null, "Personalization Data"), this.renderPersonalizationData());
   1204  }
   1205 }
   1206 class DiscoveryStreamAdminInner extends (external_React_default()).PureComponent {
   1207  constructor(props) {
   1208    super(props);
   1209    this.setState = this.setState.bind(this);
   1210  }
   1211  render() {
   1212    return /*#__PURE__*/external_React_default().createElement("div", {
   1213      className: `discoverystream-admin ${this.props.collapsed ? "collapsed" : "expanded"}`
   1214    }, /*#__PURE__*/external_React_default().createElement("main", {
   1215      className: "main-panel"
   1216    }, /*#__PURE__*/external_React_default().createElement("h1", null, "Discovery Stream Admin"), /*#__PURE__*/external_React_default().createElement("p", {
   1217      className: "helpLink"
   1218    }, /*#__PURE__*/external_React_default().createElement("span", {
   1219      className: "icon icon-small-spacer icon-info"
   1220    }), " ", /*#__PURE__*/external_React_default().createElement("span", null, "Need to access the ASRouter Admin dev tools?", " ", /*#__PURE__*/external_React_default().createElement("a", {
   1221      target: "blank",
   1222      href: "about:asrouter"
   1223    }, "Click here"))), /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement(DiscoveryStreamAdminUI, {
   1224      state: {
   1225        DiscoveryStream: this.props.DiscoveryStream,
   1226        Personalization: this.props.Personalization,
   1227        Weather: this.props.Weather,
   1228        InferredPersonalization: this.props.InferredPersonalization
   1229      },
   1230      otherPrefs: this.props.Prefs.values,
   1231      dispatch: this.props.dispatch
   1232    }))));
   1233  }
   1234 }
   1235 function CollapseToggle(props) {
   1236  const {
   1237    devtoolsCollapsed
   1238  } = props;
   1239  const label = `${devtoolsCollapsed ? "Expand" : "Collapse"} devtools`;
   1240  (0,external_React_namespaceObject.useEffect)(() => {
   1241    // Set or remove body class depending on devtoolsCollapsed state
   1242    if (devtoolsCollapsed) {
   1243      globalThis.document.body.classList.remove("no-scroll");
   1244    } else {
   1245      globalThis.document.body.classList.add("no-scroll");
   1246    }
   1247 
   1248    // Cleanup on unmount
   1249    return () => {
   1250      globalThis.document.body.classList.remove("no-scroll");
   1251    };
   1252  }, [devtoolsCollapsed]);
   1253  return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement("a", {
   1254    href: devtoolsCollapsed ? "#devtools" : "#",
   1255    title: label,
   1256    "aria-label": label,
   1257    className: `discoverystream-admin-toggle ${devtoolsCollapsed ? "expanded" : "collapsed"}`
   1258  }, /*#__PURE__*/external_React_default().createElement("span", {
   1259    className: "icon icon-devtools"
   1260  })), !devtoolsCollapsed ? /*#__PURE__*/external_React_default().createElement(DiscoveryStreamAdminInner, _extends({}, props, {
   1261    collapsed: devtoolsCollapsed
   1262  })) : null);
   1263 }
   1264 const _DiscoveryStreamAdmin = props => /*#__PURE__*/external_React_default().createElement(CollapseToggle, props);
   1265 const DiscoveryStreamAdmin = (0,external_ReactRedux_namespaceObject.connect)(state => ({
   1266  Sections: state.Sections,
   1267  DiscoveryStream: state.DiscoveryStream,
   1268  Personalization: state.Personalization,
   1269  InferredPersonalization: state.InferredPersonalization,
   1270  Prefs: state.Prefs,
   1271  Weather: state.Weather
   1272 }))(_DiscoveryStreamAdmin);
   1273 ;// CONCATENATED MODULE: ./content-src/components/ConfirmDialog/ConfirmDialog.jsx
   1274 /* This Source Code Form is subject to the terms of the Mozilla Public
   1275 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   1276 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   1277 
   1278 
   1279 
   1280 
   1281 
   1282 /**
   1283 * ConfirmDialog component.
   1284 * One primary action button, one cancel button.
   1285 *
   1286 * Content displayed is controlled by `data` prop the component receives.
   1287 * Example:
   1288 * data: {
   1289 *   // Any sort of data needed to be passed around by actions.
   1290 *   payload: site.url,
   1291 *   // Primary button AlsoToMain action.
   1292 *   action: "DELETE_HISTORY_URL",
   1293 *   // Primary button USerEvent action.
   1294 *   userEvent: "DELETE",
   1295 *   // Array of locale ids to display.
   1296 *   message_body: ["confirm_history_delete_p1", "confirm_history_delete_notice_p2"],
   1297 *   // Text for primary button.
   1298 *   confirm_button_string_id: "menu_action_delete"
   1299 * },
   1300 */
   1301 class _ConfirmDialog extends (external_React_default()).PureComponent {
   1302  constructor(props) {
   1303    super(props);
   1304    this._handleCancelBtn = this._handleCancelBtn.bind(this);
   1305    this._handleConfirmBtn = this._handleConfirmBtn.bind(this);
   1306    this.dialogRef = /*#__PURE__*/external_React_default().createRef();
   1307  }
   1308  componentDidUpdate() {
   1309    const dialogElement = this.dialogRef.current;
   1310    if (!dialogElement) {
   1311      return;
   1312    }
   1313 
   1314    // Open dialog when visible becomes true
   1315    if (this.props.visible && !dialogElement.open) {
   1316      dialogElement.showModal();
   1317    }
   1318    // Close dialog when visible becomes false
   1319    else if (!this.props.visible && dialogElement.open) {
   1320      dialogElement.close();
   1321    }
   1322  }
   1323  _handleCancelBtn() {
   1324    this.props.dispatch({
   1325      type: actionTypes.DIALOG_CANCEL
   1326    });
   1327    this.props.dispatch(actionCreators.UserEvent({
   1328      event: actionTypes.DIALOG_CANCEL,
   1329      source: this.props.data.eventSource
   1330    }));
   1331  }
   1332  _handleConfirmBtn() {
   1333    this.props.data.onConfirm.forEach(this.props.dispatch);
   1334  }
   1335  _renderModalMessage() {
   1336    const message_body = this.props.data.body_string_id;
   1337    if (!message_body) {
   1338      return null;
   1339    }
   1340    return /*#__PURE__*/external_React_default().createElement("span", null, message_body.map(msg => /*#__PURE__*/external_React_default().createElement("p", {
   1341      key: msg,
   1342      "data-l10n-id": msg
   1343    })));
   1344  }
   1345  render() {
   1346    return /*#__PURE__*/external_React_default().createElement("dialog", {
   1347      ref: this.dialogRef,
   1348      className: "confirmation-dialog",
   1349      onClick: e => {
   1350        // Close modal when clicking on the backdrop pseudo element (the background of the modal)
   1351        if (e.target === this.dialogRef.current) {
   1352          this._handleCancelBtn();
   1353        }
   1354      }
   1355    }, /*#__PURE__*/external_React_default().createElement("div", {
   1356      className: "modal"
   1357    }, /*#__PURE__*/external_React_default().createElement("section", {
   1358      className: "modal-message"
   1359    }, this.props.data.icon && /*#__PURE__*/external_React_default().createElement("span", {
   1360      className: `icon icon-spacer icon-${this.props.data.icon}`
   1361    }), this._renderModalMessage()), /*#__PURE__*/external_React_default().createElement("section", {
   1362      className: "button-group"
   1363    }, /*#__PURE__*/external_React_default().createElement("moz-button-group", null, /*#__PURE__*/external_React_default().createElement("moz-button", {
   1364      onClick: this._handleCancelBtn,
   1365      "data-l10n-id": this.props.data.cancel_button_string_id
   1366    }), /*#__PURE__*/external_React_default().createElement("moz-button", {
   1367      type: "primary",
   1368      onClick: this._handleConfirmBtn,
   1369      "data-l10n-id": this.props.data.confirm_button_string_id,
   1370      "data-l10n-args": JSON.stringify(this.props.data.confirm_button_string_args)
   1371    })))));
   1372  }
   1373 }
   1374 const ConfirmDialog = (0,external_ReactRedux_namespaceObject.connect)(state => state.Dialog)(_ConfirmDialog);
   1375 ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/DSImage/DSImage.jsx
   1376 /* This Source Code Form is subject to the terms of the Mozilla Public
   1377 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   1378 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   1379 
   1380 
   1381 const PLACEHOLDER_IMAGE_DATA_ARRAY = [{
   1382  rotation: "0deg",
   1383  offsetx: "20px",
   1384  offsety: "8px",
   1385  scale: "45%"
   1386 }, {
   1387  rotation: "54deg",
   1388  offsetx: "-26px",
   1389  offsety: "62px",
   1390  scale: "55%"
   1391 }, {
   1392  rotation: "-30deg",
   1393  offsetx: "78px",
   1394  offsety: "30px",
   1395  scale: "68%"
   1396 }, {
   1397  rotation: "-22deg",
   1398  offsetx: "0",
   1399  offsety: "92px",
   1400  scale: "60%"
   1401 }, {
   1402  rotation: "-65deg",
   1403  offsetx: "66px",
   1404  offsety: "28px",
   1405  scale: "60%"
   1406 }, {
   1407  rotation: "22deg",
   1408  offsetx: "-35px",
   1409  offsety: "62px",
   1410  scale: "52%"
   1411 }, {
   1412  rotation: "-25deg",
   1413  offsetx: "86px",
   1414  offsety: "-15px",
   1415  scale: "68%"
   1416 }];
   1417 const PLACEHOLDER_IMAGE_COLORS_ARRAY = "#0090ED #FF4F5F #2AC3A2 #FF7139 #A172FF #FFA437 #FF2A8A".split(" ");
   1418 function generateIndex({
   1419  keyCode,
   1420  max
   1421 }) {
   1422  if (!keyCode) {
   1423    // Just grab a random index if we cannot generate an index from a key.
   1424    return Math.floor(Math.random() * max);
   1425  }
   1426  const hashStr = str => {
   1427    let hash = 0;
   1428    for (let i = 0; i < str.length; i++) {
   1429      let charCode = str.charCodeAt(i);
   1430      hash += charCode;
   1431    }
   1432    return hash;
   1433  };
   1434  const hash = hashStr(keyCode);
   1435  return hash % max;
   1436 }
   1437 function PlaceholderImage({
   1438  urlKey,
   1439  titleKey
   1440 }) {
   1441  const dataIndex = generateIndex({
   1442    keyCode: urlKey,
   1443    max: PLACEHOLDER_IMAGE_DATA_ARRAY.length
   1444  });
   1445  const colorIndex = generateIndex({
   1446    keyCode: titleKey,
   1447    max: PLACEHOLDER_IMAGE_COLORS_ARRAY.length
   1448  });
   1449  const {
   1450    rotation,
   1451    offsetx,
   1452    offsety,
   1453    scale
   1454  } = PLACEHOLDER_IMAGE_DATA_ARRAY[dataIndex];
   1455  const color = PLACEHOLDER_IMAGE_COLORS_ARRAY[colorIndex];
   1456  const style = {
   1457    "--placeholderBackgroundColor": color,
   1458    "--placeholderBackgroundRotation": rotation,
   1459    "--placeholderBackgroundOffsetx": offsetx,
   1460    "--placeholderBackgroundOffsety": offsety,
   1461    "--placeholderBackgroundScale": scale
   1462  };
   1463  return /*#__PURE__*/external_React_default().createElement("div", {
   1464    style: style,
   1465    className: "placeholder-image"
   1466  });
   1467 }
   1468 class DSImage extends (external_React_default()).PureComponent {
   1469  constructor(props) {
   1470    super(props);
   1471    this.onOptimizedImageError = this.onOptimizedImageError.bind(this);
   1472    this.onNonOptimizedImageError = this.onNonOptimizedImageError.bind(this);
   1473    this.onLoad = this.onLoad.bind(this);
   1474    this.state = {
   1475      isLoaded: false,
   1476      optimizedImageFailed: false,
   1477      useTransition: false
   1478    };
   1479  }
   1480  onIdleCallback() {
   1481    if (!this.state.isLoaded) {
   1482      this.setState({
   1483        useTransition: true
   1484      });
   1485    }
   1486  }
   1487 
   1488  // Wraps the image url with the Pocket proxy to both resize and crop the image.
   1489  reformatImageURL(url, width, height) {
   1490    const smart = this.props.smartCrop ? "smart/" : "";
   1491    // Change the image URL to request a size tailored for the parent container width
   1492    // Also: force JPEG, quality 60, no upscaling, no EXIF data
   1493    // Uses Thumbor: https://thumbor.readthedocs.io/en/latest/usage.html
   1494    const formattedUrl = `https://img-getpocket.cdn.mozilla.net/${width}x${height}/${smart}filters:format(jpeg):quality(60):no_upscale():strip_exif()/${encodeURIComponent(url)}`;
   1495    return this.secureImageURL(formattedUrl);
   1496  }
   1497 
   1498  // Wraps the image URL with the moz-cached-ohttp:// protocol.
   1499  // This enables Firefox to load resources over Oblivious HTTP (OHTTP),
   1500  // providing privacy-preserving resource loading.
   1501  // Applied only when inferred personalization is enabled.
   1502  // See: https://firefox-source-docs.mozilla.org/browser/components/mozcachedohttp/docs/index.html
   1503  secureImageURL(url) {
   1504    if (!this.props.secureImage) {
   1505      return url;
   1506    }
   1507    return `moz-cached-ohttp://newtab-image/?url=${encodeURIComponent(url)}`;
   1508  }
   1509  componentDidMount() {
   1510    this.idleCallbackId = this.props.windowObj.requestIdleCallback(this.onIdleCallback.bind(this));
   1511  }
   1512  componentWillUnmount() {
   1513    if (this.idleCallbackId) {
   1514      this.props.windowObj.cancelIdleCallback(this.idleCallbackId);
   1515    }
   1516  }
   1517  render() {
   1518    let classNames = `ds-image
   1519      ${this.props.extraClassNames ? ` ${this.props.extraClassNames}` : ``}
   1520      ${this.state && this.state.useTransition ? ` use-transition` : ``}
   1521      ${this.state && this.state.isLoaded ? ` loaded` : ``}
   1522    `;
   1523    let img;
   1524    if (this.state) {
   1525      if (this.props.optimize && this.props.rawSource && !this.state.optimizedImageFailed) {
   1526        const baseSource = this.props.rawSource;
   1527 
   1528        // We don't care about securing this.props.source, as this exclusivly
   1529        // comes from an older service that is not personalized.
   1530        // This can also return a non secure url if this functionality is not enabled.
   1531        const securedSource = this.secureImageURL(baseSource);
   1532        let sizeRules = [];
   1533        let srcSetRules = [];
   1534        for (let rule of this.props.sizes) {
   1535          let {
   1536            mediaMatcher,
   1537            width,
   1538            height
   1539          } = rule;
   1540          let sizeRule = `${mediaMatcher} ${width}px`;
   1541          sizeRules.push(sizeRule);
   1542          let srcSetRule = `${this.reformatImageURL(baseSource, width, height)} ${width}w`;
   1543          let srcSetRule2x = `${this.reformatImageURL(baseSource, width * 2, height * 2)} ${width * 2}w`;
   1544          srcSetRules.push(srcSetRule);
   1545          srcSetRules.push(srcSetRule2x);
   1546        }
   1547        if (this.props.sizes.length) {
   1548          // We have to supply a fallback in the very unlikely event that none of
   1549          // the media queries match. The smallest dimension was chosen arbitrarily.
   1550          sizeRules.push(`${this.props.sizes[this.props.sizes.length - 1].width}px`);
   1551        }
   1552        img = /*#__PURE__*/external_React_default().createElement("img", {
   1553          loading: "lazy",
   1554          alt: this.props.alt_text,
   1555          crossOrigin: "anonymous",
   1556          onLoad: this.onLoad,
   1557          onError: this.onOptimizedImageError,
   1558          sizes: sizeRules.join(","),
   1559          src: securedSource,
   1560          srcSet: srcSetRules.join(",")
   1561        });
   1562      } else if (this.props.source && !this.state.nonOptimizedImageFailed) {
   1563        img = /*#__PURE__*/external_React_default().createElement("img", {
   1564          loading: "lazy",
   1565          alt: this.props.alt_text,
   1566          crossOrigin: "anonymous",
   1567          onLoad: this.onLoad,
   1568          onError: this.onNonOptimizedImageError,
   1569          src: this.props.source
   1570        });
   1571      } else {
   1572        // We consider a failed to load img or source without an image as loaded.
   1573        classNames = `${classNames} loaded`;
   1574        // Remove the img element if we have no source. Render a placeholder instead.
   1575        // This only happens for recent saves without a source.
   1576        if (this.props.isRecentSave && !this.props.rawSource && !this.props.source) {
   1577          img = /*#__PURE__*/external_React_default().createElement(PlaceholderImage, {
   1578            urlKey: this.props.url,
   1579            titleKey: this.props.title
   1580          });
   1581        } else {
   1582          img = /*#__PURE__*/external_React_default().createElement("div", {
   1583            className: "broken-image"
   1584          });
   1585        }
   1586      }
   1587    }
   1588    return /*#__PURE__*/external_React_default().createElement("picture", {
   1589      className: classNames
   1590    }, img);
   1591  }
   1592  onOptimizedImageError() {
   1593    // This will trigger a re-render and the unoptimized 450px image will be used as a fallback
   1594    this.setState({
   1595      optimizedImageFailed: true
   1596    });
   1597  }
   1598  onNonOptimizedImageError() {
   1599    this.setState({
   1600      nonOptimizedImageFailed: true
   1601    });
   1602  }
   1603  onLoad() {
   1604    this.setState({
   1605      isLoaded: true
   1606    });
   1607  }
   1608 }
   1609 DSImage.defaultProps = {
   1610  source: null,
   1611  // The current source style from Pocket API (always 450px)
   1612  rawSource: null,
   1613  // Unadulterated image URL to filter through Thumbor
   1614  extraClassNames: null,
   1615  // Additional classnames to append to component
   1616  optimize: true,
   1617  // Measure parent container to request exact sizes
   1618  alt_text: null,
   1619  windowObj: window,
   1620  // Added to support unit tests
   1621  sizes: []
   1622 };
   1623 ;// CONCATENATED MODULE: ./content-src/components/ContextMenu/ContextMenu.jsx
   1624 /* This Source Code Form is subject to the terms of the Mozilla Public
   1625 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   1626 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   1627 
   1628 
   1629 
   1630 class ContextMenu extends (external_React_default()).PureComponent {
   1631  constructor(props) {
   1632    super(props);
   1633    this.hideContext = this.hideContext.bind(this);
   1634    this.onShow = this.onShow.bind(this);
   1635    this.onClick = this.onClick.bind(this);
   1636  }
   1637  hideContext() {
   1638    this.props.onUpdate(false);
   1639  }
   1640  onShow() {
   1641    if (this.props.onShow) {
   1642      this.props.onShow();
   1643    }
   1644  }
   1645  componentDidMount() {
   1646    this.onShow();
   1647    setTimeout(() => {
   1648      globalThis.addEventListener("click", this.hideContext);
   1649    }, 0);
   1650  }
   1651  componentWillUnmount() {
   1652    globalThis.removeEventListener("click", this.hideContext);
   1653  }
   1654  onClick(event) {
   1655    // Eat all clicks on the context menu so they don't bubble up to window.
   1656    // This prevents the context menu from closing when clicking disabled items
   1657    // or the separators.
   1658    event.stopPropagation();
   1659  }
   1660  render() {
   1661    // Disabling focus on the menu span allows the first tab to focus on the first menu item instead of the wrapper.
   1662    return (
   1663      /*#__PURE__*/
   1664      // eslint-disable-next-line jsx-a11y/interactive-supports-focus
   1665      external_React_default().createElement("span", {
   1666        className: "context-menu"
   1667      }, /*#__PURE__*/external_React_default().createElement("ul", {
   1668        role: "menu",
   1669        onClick: this.onClick,
   1670        onKeyDown: this.onClick,
   1671        className: "context-menu-list"
   1672      }, this.props.options.map((option, i) => option.type === "separator" ? /*#__PURE__*/external_React_default().createElement("li", {
   1673        key: i,
   1674        className: "separator",
   1675        role: "separator"
   1676      }) : option.type !== "empty" && /*#__PURE__*/external_React_default().createElement(ContextMenuItem, {
   1677        key: i,
   1678        option: option,
   1679        hideContext: this.hideContext,
   1680        keyboardAccess: this.props.keyboardAccess
   1681      }))))
   1682    );
   1683  }
   1684 }
   1685 class _ContextMenuItem extends (external_React_default()).PureComponent {
   1686  constructor(props) {
   1687    super(props);
   1688    this.onClick = this.onClick.bind(this);
   1689    this.onKeyDown = this.onKeyDown.bind(this);
   1690    this.onKeyUp = this.onKeyUp.bind(this);
   1691    this.focusFirst = this.focusFirst.bind(this);
   1692  }
   1693  onClick(event) {
   1694    this.props.hideContext();
   1695    this.props.option.onClick(event);
   1696  }
   1697 
   1698  // Focus the first menu item if the menu was accessed via the keyboard.
   1699  focusFirst(button) {
   1700    if (this.props.keyboardAccess && button) {
   1701      button.focus();
   1702    }
   1703  }
   1704 
   1705  // This selects the correct node based on the key pressed
   1706  focusSibling(target, key) {
   1707    const {
   1708      parentNode
   1709    } = target;
   1710    const closestSiblingSelector = key === "ArrowUp" ? "previousSibling" : "nextSibling";
   1711    if (!parentNode[closestSiblingSelector]) {
   1712      return;
   1713    }
   1714    if (parentNode[closestSiblingSelector].firstElementChild) {
   1715      parentNode[closestSiblingSelector].firstElementChild.focus();
   1716    } else {
   1717      parentNode[closestSiblingSelector][closestSiblingSelector].firstElementChild.focus();
   1718    }
   1719  }
   1720  onKeyDown(event) {
   1721    const {
   1722      option
   1723    } = this.props;
   1724    switch (event.key) {
   1725      case "Tab":
   1726        // tab goes down in context menu, shift + tab goes up in context menu
   1727        // if we're on the last item, one more tab will close the context menu
   1728        // similarly, if we're on the first item, one more shift + tab will close it
   1729        if (event.shiftKey && option.first || !event.shiftKey && option.last) {
   1730          this.props.hideContext();
   1731        }
   1732        break;
   1733      case "ArrowUp":
   1734      case "ArrowDown":
   1735        event.preventDefault();
   1736        this.focusSibling(event.target, event.key);
   1737        break;
   1738      case "Enter":
   1739      case " ":
   1740        event.preventDefault();
   1741        this.props.hideContext();
   1742        option.onClick();
   1743        break;
   1744      case "Escape":
   1745        this.props.hideContext();
   1746        break;
   1747    }
   1748  }
   1749 
   1750  // Prevents the default behavior of spacebar
   1751  // scrolling the page & auto-triggering buttons.
   1752  onKeyUp(event) {
   1753    if (event.key === " ") {
   1754      event.preventDefault();
   1755    }
   1756  }
   1757  render() {
   1758    const {
   1759      option
   1760    } = this.props;
   1761    const className = [option.disabled ? "disabled" : ""].join(" ");
   1762    return /*#__PURE__*/external_React_default().createElement("li", {
   1763      role: "presentation",
   1764      className: "context-menu-item"
   1765    }, /*#__PURE__*/external_React_default().createElement("button", {
   1766      role: "menuitem",
   1767      className: className,
   1768      onClick: this.onClick,
   1769      onKeyDown: this.onKeyDown,
   1770      onKeyUp: this.onKeyUp,
   1771      ref: option.first ? this.focusFirst : null,
   1772      "aria-haspopup": option.id === "newtab-menu-edit-topsites" ? "dialog" : null
   1773    }, /*#__PURE__*/external_React_default().createElement("span", {
   1774      "data-l10n-id": option.string_id || option.id
   1775    })));
   1776  }
   1777 }
   1778 const ContextMenuItem = (0,external_ReactRedux_namespaceObject.connect)(state => ({
   1779  Prefs: state.Prefs
   1780 }))(_ContextMenuItem);
   1781 ;// CONCATENATED MODULE: ./content-src/lib/link-menu-options.mjs
   1782 /* This Source Code Form is subject to the terms of the Mozilla Public
   1783 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   1784 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   1785 
   1786 
   1787 
   1788 const _OpenInPrivateWindow = site => ({
   1789  id: "newtab-menu-open-new-private-window",
   1790  icon: "new-window-private",
   1791  action: actionCreators.OnlyToMain({
   1792    type: actionTypes.OPEN_PRIVATE_WINDOW,
   1793    data: {
   1794      url: site.url,
   1795      referrer: site.referrer,
   1796      event_source: "CONTEXT_MENU",
   1797    },
   1798  }),
   1799  userEvent: "OPEN_PRIVATE_WINDOW",
   1800 });
   1801 
   1802 /**
   1803 * List of functions that return items that can be included as menu options in a
   1804 * LinkMenu. All functions take the site as the first parameter, and optionally
   1805 * the index of the site.
   1806 */
   1807 const LinkMenuOptions = {
   1808  Separator: () => ({ type: "separator" }),
   1809  EmptyItem: () => ({ type: "empty" }),
   1810  ShowPrivacyInfo: () => ({
   1811    id: "newtab-menu-show-privacy-info",
   1812    icon: "info",
   1813    action: {
   1814      type: actionTypes.SHOW_PRIVACY_INFO,
   1815    },
   1816    userEvent: "SHOW_PRIVACY_INFO",
   1817  }),
   1818  AboutSponsored: site => ({
   1819    id: "newtab-menu-show-privacy-info",
   1820    icon: "info",
   1821    action: actionCreators.AlsoToMain({
   1822      type: actionTypes.ABOUT_SPONSORED_TOP_SITES,
   1823      data: {
   1824        advertiser_name: (site.label || site.hostname).toLocaleLowerCase(),
   1825        position: site.sponsored_position,
   1826        tile_id: site.sponsored_tile_id,
   1827        block_key: site.block_key,
   1828      },
   1829    }),
   1830    userEvent: "TOPSITE_SPONSOR_INFO",
   1831  }),
   1832  RemoveBookmark: site => ({
   1833    id: "newtab-menu-remove-bookmark",
   1834    icon: "bookmark-added",
   1835    action: actionCreators.AlsoToMain({
   1836      type: actionTypes.DELETE_BOOKMARK_BY_ID,
   1837      data: site.bookmarkGuid,
   1838    }),
   1839    userEvent: "BOOKMARK_DELETE",
   1840  }),
   1841  AddBookmark: site => ({
   1842    id: "newtab-menu-bookmark",
   1843    icon: "bookmark-hollow",
   1844    action: actionCreators.AlsoToMain({
   1845      type: actionTypes.BOOKMARK_URL,
   1846      data: { url: site.url, title: site.title, type: site.type },
   1847    }),
   1848    userEvent: "BOOKMARK_ADD",
   1849  }),
   1850  OpenInNewWindow: site => ({
   1851    id: "newtab-menu-open-new-window",
   1852    icon: "new-window",
   1853    action: actionCreators.AlsoToMain({
   1854      type: actionTypes.OPEN_NEW_WINDOW,
   1855      data: {
   1856        card_type: site.card_type,
   1857        referrer: site.referrer,
   1858        typedBonus: site.typedBonus,
   1859        url: site.url,
   1860        is_sponsored: !!site.sponsored_tile_id,
   1861        event_source: "CONTEXT_MENU",
   1862        topic: site.topic,
   1863        firstVisibleTimestamp: site.firstVisibleTimestamp,
   1864        tile_id: site.tile_id,
   1865        recommendation_id: site.recommendation_id,
   1866        scheduled_corpus_item_id: site.scheduled_corpus_item_id,
   1867        corpus_item_id: site.corpus_item_id,
   1868        received_rank: site.received_rank,
   1869        recommended_at: site.recommended_at,
   1870        format: site.format,
   1871        ...(site.flight_id ? { flight_id: site.flight_id } : {}),
   1872        is_pocket_card: site.type === "CardGrid",
   1873        ...(site.section
   1874          ? {
   1875              section: site.section,
   1876              section_position: site.section_position,
   1877              is_section_followed: site.is_section_followed,
   1878            }
   1879          : {}),
   1880      },
   1881    }),
   1882    userEvent: "OPEN_NEW_WINDOW",
   1883  }),
   1884 
   1885  // This blocks the url for regular stories,
   1886  // but also sends a message to DiscoveryStream with flight_id.
   1887  // If DiscoveryStream sees this message for a flight_id
   1888  // it also blocks it on the flight_id.
   1889  BlockUrl: (site, index, eventSource) => {
   1890    return LinkMenuOptions.BlockUrls([site], index, eventSource);
   1891  },
   1892  // Same as BlockUrl, except can work on an array of sites.
   1893  BlockUrls: (tiles, pos, eventSource) => ({
   1894    id: "newtab-menu-dismiss",
   1895    icon: "dismiss",
   1896    action: actionCreators.AlsoToMain({
   1897      type: actionTypes.BLOCK_URL,
   1898      source: eventSource,
   1899      data: tiles.map(site => ({
   1900        url: site.original_url || site.open_url || site.url,
   1901        // pocket_id is only for pocket stories being in highlights, and then dismissed.
   1902        pocket_id: site.pocket_id,
   1903        tile_id: site.tile_id,
   1904        ...(site.block_key ? { block_key: site.block_key } : {}),
   1905        recommendation_id: site.recommendation_id,
   1906        scheduled_corpus_item_id: site.scheduled_corpus_item_id,
   1907        corpus_item_id: site.corpus_item_id,
   1908        received_rank: site.received_rank,
   1909        recommended_at: site.recommended_at,
   1910        // used by PlacesFeed and TopSitesFeed for sponsored top sites blocking.
   1911        isSponsoredTopSite: site.sponsored_position,
   1912        type: site.type,
   1913        card_type: site.card_type,
   1914        ...(site.shim && site.shim.delete ? { shim: site.shim.delete } : {}),
   1915        ...(site.flight_id ? { flight_id: site.flight_id } : {}),
   1916        // If not sponsored, hostname could be anything (Cat3 Data!).
   1917        // So only put in advertiser_name for sponsored topsites.
   1918        ...(site.sponsored_position
   1919          ? {
   1920              advertiser_name: (
   1921                site.label || site.hostname
   1922              )?.toLocaleLowerCase(),
   1923            }
   1924          : {}),
   1925        position: pos,
   1926        ...(site.sponsored_tile_id ? { tile_id: site.sponsored_tile_id } : {}),
   1927        is_pocket_card: site.type === "CardGrid",
   1928        ...(site.format ? { format: site.format } : {}),
   1929        ...(site.section
   1930          ? {
   1931              section: site.section,
   1932              section_position: site.section_position,
   1933              is_section_followed: site.is_section_followed,
   1934            }
   1935          : {}),
   1936      })),
   1937    }),
   1938    impression: actionCreators.ImpressionStats({
   1939      source: eventSource,
   1940      block: 0,
   1941      tiles: tiles.map((site, index) => ({
   1942        id: site.guid,
   1943        pos: pos + index,
   1944        ...(site.shim && site.shim.delete ? { shim: site.shim.delete } : {}),
   1945      })),
   1946    }),
   1947    userEvent: "BLOCK",
   1948  }),
   1949 
   1950  // This is the "Dismiss" action for leaderboard/billboard ads.
   1951  BlockAdUrl: (site, pos, eventSource) => ({
   1952    id: "newtab-menu-dismiss",
   1953    icon: "dismiss",
   1954    action: actionCreators.AlsoToMain({
   1955      type: actionTypes.BLOCK_URL,
   1956      data: [site],
   1957    }),
   1958    impression: actionCreators.ImpressionStats({
   1959      source: eventSource,
   1960      block: 0,
   1961      tiles: [
   1962        {
   1963          id: site.guid,
   1964          pos,
   1965          ...(site.shim && site.shim.save ? { shim: site.shim.save } : {}),
   1966        },
   1967      ],
   1968    }),
   1969    userEvent: "BLOCK",
   1970  }),
   1971 
   1972  // This is an option for web extentions which will result in remove items from
   1973  // memory and notify the web extenion, rather than using the built-in block list.
   1974  WebExtDismiss: (site, index, eventSource) => ({
   1975    id: "menu_action_webext_dismiss",
   1976    string_id: "newtab-menu-dismiss",
   1977    icon: "dismiss",
   1978    action: actionCreators.WebExtEvent(actionTypes.WEBEXT_DISMISS, {
   1979      source: eventSource,
   1980      url: site.url,
   1981      action_position: index,
   1982    }),
   1983  }),
   1984  DeleteUrl: (site, index, eventSource, isEnabled, siteInfo) => ({
   1985    id: "newtab-menu-delete-history",
   1986    icon: "delete",
   1987    action: {
   1988      type: actionTypes.DIALOG_OPEN,
   1989      data: {
   1990        onConfirm: [
   1991          actionCreators.AlsoToMain({
   1992            type: actionTypes.DELETE_HISTORY_URL,
   1993            data: {
   1994              url: site.url,
   1995              pocket_id: site.pocket_id,
   1996              forceBlock: site.bookmarkGuid,
   1997            },
   1998          }),
   1999          actionCreators.UserEvent(
   2000            Object.assign(
   2001              { event: "DELETE", source: eventSource, action_position: index },
   2002              siteInfo
   2003            )
   2004          ),
   2005          // Also broadcast that this url has been deleted so that
   2006          // the confirmation dialog knows it needs to disappear now.
   2007          actionCreators.AlsoToMain({
   2008            type: actionTypes.DIALOG_CLOSE,
   2009          }),
   2010        ],
   2011        eventSource,
   2012        body_string_id: [
   2013          "newtab-confirm-delete-history-p1",
   2014          "newtab-confirm-delete-history-p2",
   2015        ],
   2016        confirm_button_string_id: "newtab-topsites-delete-history-button",
   2017        cancel_button_string_id: "newtab-topsites-cancel-button",
   2018        icon: "modal-delete",
   2019      },
   2020    },
   2021    userEvent: "DIALOG_OPEN",
   2022  }),
   2023  ShowFile: site => ({
   2024    id: "newtab-menu-show-file",
   2025    icon: "search",
   2026    action: actionCreators.OnlyToMain({
   2027      type: actionTypes.SHOW_DOWNLOAD_FILE,
   2028      data: { url: site.url },
   2029    }),
   2030  }),
   2031  OpenFile: site => ({
   2032    id: "newtab-menu-open-file",
   2033    icon: "open-file",
   2034    action: actionCreators.OnlyToMain({
   2035      type: actionTypes.OPEN_DOWNLOAD_FILE,
   2036      data: { url: site.url },
   2037    }),
   2038  }),
   2039  CopyDownloadLink: site => ({
   2040    id: "newtab-menu-copy-download-link",
   2041    icon: "copy",
   2042    action: actionCreators.OnlyToMain({
   2043      type: actionTypes.COPY_DOWNLOAD_LINK,
   2044      data: { url: site.url },
   2045    }),
   2046  }),
   2047  GoToDownloadPage: site => ({
   2048    id: "newtab-menu-go-to-download-page",
   2049    icon: "download",
   2050    action: actionCreators.OnlyToMain({
   2051      type: actionTypes.OPEN_LINK,
   2052      data: { url: site.referrer },
   2053    }),
   2054    disabled: !site.referrer,
   2055  }),
   2056  RemoveDownload: site => ({
   2057    id: "newtab-menu-remove-download",
   2058    icon: "delete",
   2059    action: actionCreators.OnlyToMain({
   2060      type: actionTypes.REMOVE_DOWNLOAD_FILE,
   2061      data: { url: site.url },
   2062    }),
   2063  }),
   2064  PinTopSite: (site, index) => ({
   2065    id: "newtab-menu-pin",
   2066    icon: "pin",
   2067    action: actionCreators.AlsoToMain({
   2068      type: actionTypes.TOP_SITES_PIN,
   2069      data: {
   2070        site,
   2071        index,
   2072      },
   2073    }),
   2074    userEvent: "PIN",
   2075  }),
   2076  UnpinTopSite: site => ({
   2077    id: "newtab-menu-unpin",
   2078    icon: "unpin",
   2079    action: actionCreators.AlsoToMain({
   2080      type: actionTypes.TOP_SITES_UNPIN,
   2081      data: { site: { url: site.url } },
   2082    }),
   2083    userEvent: "UNPIN",
   2084  }),
   2085  EditTopSite: (site, index) => ({
   2086    id: "newtab-menu-edit-topsites",
   2087    icon: "edit",
   2088    action: {
   2089      type: actionTypes.TOP_SITES_EDIT,
   2090      data: { index },
   2091    },
   2092  }),
   2093  CheckBookmark: site =>
   2094    site.bookmarkGuid
   2095      ? LinkMenuOptions.RemoveBookmark(site)
   2096      : LinkMenuOptions.AddBookmark(site),
   2097  CheckPinTopSite: (site, index) =>
   2098    site.isPinned
   2099      ? LinkMenuOptions.UnpinTopSite(site)
   2100      : LinkMenuOptions.PinTopSite(site, index),
   2101  OpenInPrivateWindow: (site, index, eventSource, isEnabled) =>
   2102    isEnabled ? _OpenInPrivateWindow(site) : LinkMenuOptions.EmptyItem(),
   2103  ChangeWeatherLocation: () => ({
   2104    id: "newtab-weather-menu-change-location",
   2105    action: actionCreators.BroadcastToContent({
   2106      type: actionTypes.WEATHER_SEARCH_ACTIVE,
   2107      data: true,
   2108    }),
   2109  }),
   2110  DetectLocation: () => ({
   2111    id: "newtab-weather-menu-detect-my-location",
   2112    action: actionCreators.AlsoToMain({
   2113      type: actionTypes.WEATHER_USER_OPT_IN_LOCATION,
   2114    }),
   2115    userEvent: "WEATHER_DETECT_LOCATION",
   2116  }),
   2117  ChangeWeatherDisplaySimple: () => ({
   2118    id: "newtab-weather-menu-change-weather-display-simple",
   2119    action: actionCreators.OnlyToMain({
   2120      type: actionTypes.SET_PREF,
   2121      data: {
   2122        name: "weather.display",
   2123        value: "simple",
   2124      },
   2125    }),
   2126  }),
   2127  ChangeWeatherDisplayDetailed: () => ({
   2128    id: "newtab-weather-menu-change-weather-display-detailed",
   2129    action: actionCreators.OnlyToMain({
   2130      type: actionTypes.SET_PREF,
   2131      data: {
   2132        name: "weather.display",
   2133        value: "detailed",
   2134      },
   2135    }),
   2136  }),
   2137  ChangeTempUnitFahrenheit: () => ({
   2138    id: "newtab-weather-menu-change-temperature-units-fahrenheit",
   2139    action: actionCreators.OnlyToMain({
   2140      type: actionTypes.SET_PREF,
   2141      data: {
   2142        name: "weather.temperatureUnits",
   2143        value: "f",
   2144      },
   2145    }),
   2146  }),
   2147  ChangeTempUnitCelsius: () => ({
   2148    id: "newtab-weather-menu-change-temperature-units-celsius",
   2149    action: actionCreators.OnlyToMain({
   2150      type: actionTypes.SET_PREF,
   2151      data: {
   2152        name: "weather.temperatureUnits",
   2153        value: "c",
   2154      },
   2155    }),
   2156  }),
   2157  HideWeather: () => ({
   2158    id: "newtab-weather-menu-hide-weather",
   2159    action: actionCreators.OnlyToMain({
   2160      type: actionTypes.SET_PREF,
   2161      data: {
   2162        name: "showWeather",
   2163        value: false,
   2164      },
   2165    }),
   2166  }),
   2167  OpenLearnMoreURL: site => ({
   2168    id: "newtab-weather-menu-learn-more",
   2169    action: actionCreators.OnlyToMain({
   2170      type: actionTypes.OPEN_LINK,
   2171      data: { url: site.url },
   2172    }),
   2173  }),
   2174  SectionBlock: ({
   2175    sectionPersonalization,
   2176    sectionKey,
   2177    sectionPosition,
   2178    title,
   2179  }) => ({
   2180    id: "newtab-menu-section-block",
   2181    icon: "delete",
   2182    action: {
   2183      // Open the confirmation dialog to block a section.
   2184      type: actionTypes.DIALOG_OPEN,
   2185      data: {
   2186        onConfirm: [
   2187          // Once the user confirmed their intention to block this section,
   2188          // update their preferences.
   2189          actionCreators.AlsoToMain({
   2190            type: actionTypes.SECTION_PERSONALIZATION_SET,
   2191            data: {
   2192              ...sectionPersonalization,
   2193              [sectionKey]: {
   2194                isBlocked: true,
   2195                isFollowed: false,
   2196              },
   2197            },
   2198          }),
   2199          // Telemetry
   2200          actionCreators.OnlyToMain({
   2201            type: actionTypes.BLOCK_SECTION,
   2202            data: {
   2203              section: sectionKey,
   2204              section_position: sectionPosition,
   2205              event_source: "CONTEXT_MENU",
   2206            },
   2207          }),
   2208          // Also broadcast that this section has been blocked so that
   2209          // the confirmation dialog knows it needs to disappear now.
   2210          actionCreators.AlsoToMain({
   2211            type: actionTypes.DIALOG_CLOSE,
   2212          }),
   2213        ],
   2214        // Pass Fluent strings to ConfirmDialog component for the copy
   2215        // of the prompt to block sections.
   2216        body_string_id: [
   2217          "newtab-section-confirm-block-topic-p1",
   2218          "newtab-section-confirm-block-topic-p2",
   2219        ],
   2220        confirm_button_string_id: "newtab-section-block-topic-button",
   2221        confirm_button_string_args: { topic: title },
   2222        cancel_button_string_id: "newtab-section-cancel-button",
   2223      },
   2224    },
   2225    userEvent: "DIALOG_OPEN",
   2226  }),
   2227  SectionUnfollow: ({
   2228    sectionPersonalization,
   2229    sectionKey,
   2230    sectionPosition,
   2231  }) => ({
   2232    id: "newtab-menu-section-unfollow",
   2233    action: actionCreators.AlsoToMain({
   2234      type: actionTypes.SECTION_PERSONALIZATION_SET,
   2235      data: (({ [sectionKey]: _sectionKey, ...remaining }) => remaining)(
   2236        sectionPersonalization
   2237      ),
   2238    }),
   2239    impression: actionCreators.OnlyToMain({
   2240      type: actionTypes.UNFOLLOW_SECTION,
   2241      data: {
   2242        section: sectionKey,
   2243        section_position: sectionPosition,
   2244        event_source: "CONTEXT_MENU",
   2245      },
   2246    }),
   2247  }),
   2248  ManageSponsoredContent: () => ({
   2249    id: "newtab-menu-manage-sponsored-content",
   2250    action: actionCreators.OnlyToMain({ type: actionTypes.SETTINGS_OPEN }),
   2251    userEvent: "OPEN_NEWTAB_PREFS",
   2252  }),
   2253  OurSponsorsAndYourPrivacy: () => ({
   2254    id: "newtab-menu-our-sponsors-and-your-privacy",
   2255    action: actionCreators.OnlyToMain({
   2256      type: actionTypes.OPEN_LINK,
   2257      data: {
   2258        url: "https://support.mozilla.org/kb/pocket-sponsored-stories-new-tabs",
   2259      },
   2260    }),
   2261    userEvent: "CLICK_PRIVACY_INFO",
   2262  }),
   2263  ReportAd: site => {
   2264    return {
   2265      id: "newtab-menu-report-this-ad",
   2266      action: actionCreators.AlsoToMain({
   2267        type: actionTypes.REPORT_AD_OPEN,
   2268        data: {
   2269          card_type: site.card_type,
   2270          position: site.position,
   2271          reporting_url: site.shim.report,
   2272          url: site.url,
   2273        },
   2274      }),
   2275    };
   2276  },
   2277 
   2278  ReportContent: site => {
   2279    return {
   2280      id: "newtab-menu-report",
   2281      action: actionCreators.AlsoToMain({
   2282        type: actionTypes.REPORT_CONTENT_OPEN,
   2283        data: {
   2284          card_type: site.card_type,
   2285          corpus_item_id: site.corpus_item_id,
   2286          scheduled_corpus_item_id: site.scheduled_corpus_item_id,
   2287          section_position: site.section_position,
   2288          section: site.section,
   2289          title: site.title,
   2290          topic: site.topic,
   2291          url: site.url,
   2292        },
   2293      }),
   2294    };
   2295  },
   2296 };
   2297 
   2298 ;// CONCATENATED MODULE: ./content-src/components/LinkMenu/LinkMenu.jsx
   2299 /* This Source Code Form is subject to the terms of the Mozilla Public
   2300 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   2301 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   2302 
   2303 
   2304 
   2305 
   2306 
   2307 
   2308 const DEFAULT_SITE_MENU_OPTIONS = ["CheckPinTopSite", "EditTopSite", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl"];
   2309 class _LinkMenu extends (external_React_default()).PureComponent {
   2310  getOptions() {
   2311    const {
   2312      props
   2313    } = this;
   2314    const {
   2315      site,
   2316      index,
   2317      source,
   2318      isPrivateBrowsingEnabled,
   2319      siteInfo,
   2320      platform,
   2321      dispatch,
   2322      options,
   2323      shouldSendImpressionStats,
   2324      userEvent = actionCreators.UserEvent
   2325    } = props;
   2326 
   2327    // Handle special case of default site
   2328    const propOptions = site.isDefault && !site.searchTopSite && !site.sponsored_position ? DEFAULT_SITE_MENU_OPTIONS : options;
   2329    const linkMenuOptions = propOptions.map(o => LinkMenuOptions[o](site, index, source, isPrivateBrowsingEnabled, siteInfo, platform)).map(option => {
   2330      const {
   2331        action,
   2332        impression,
   2333        id,
   2334        type,
   2335        userEvent: eventName
   2336      } = option;
   2337      if (!type && id) {
   2338        option.onClick = (event = {}) => {
   2339          const {
   2340            ctrlKey,
   2341            metaKey,
   2342            shiftKey,
   2343            button
   2344          } = event;
   2345          // Only send along event info if there's something non-default to send
   2346          if (ctrlKey || metaKey || shiftKey || button === 1) {
   2347            action.data = Object.assign({
   2348              event: {
   2349                ctrlKey,
   2350                metaKey,
   2351                shiftKey,
   2352                button
   2353              }
   2354            }, action.data);
   2355          }
   2356          dispatch(action);
   2357          if (eventName) {
   2358            let value;
   2359            // Bug 1958135: Pass additional info to ac.OPEN_NEW_WINDOW event
   2360            if (action.type === "OPEN_NEW_WINDOW") {
   2361              const {
   2362                card_type,
   2363                corpus_item_id,
   2364                event_source,
   2365                fetchTimestamp,
   2366                firstVisibleTimestamp,
   2367                format,
   2368                is_section_followed,
   2369                received_rank,
   2370                recommendation_id,
   2371                recommended_at,
   2372                scheduled_corpus_item_id,
   2373                section_position,
   2374                section,
   2375                selected_topics,
   2376                tile_id,
   2377                topic
   2378              } = action.data;
   2379              value = {
   2380                card_type,
   2381                corpus_item_id,
   2382                event_source,
   2383                fetchTimestamp,
   2384                firstVisibleTimestamp,
   2385                format,
   2386                received_rank,
   2387                recommendation_id,
   2388                recommended_at,
   2389                scheduled_corpus_item_id,
   2390                ...(section ? {
   2391                  is_section_followed,
   2392                  section_position,
   2393                  section
   2394                } : {}),
   2395                selected_topics: selected_topics ? selected_topics : "",
   2396                tile_id,
   2397                topic
   2398              };
   2399            } else {
   2400              value = {
   2401                card_type: site.flight_id ? "spoc" : "organic"
   2402              };
   2403            }
   2404            const userEventData = Object.assign({
   2405              event: eventName,
   2406              source,
   2407              action_position: index,
   2408              value
   2409            }, siteInfo);
   2410            dispatch(userEvent(userEventData));
   2411            if (impression && shouldSendImpressionStats) {
   2412              dispatch(impression);
   2413            }
   2414          }
   2415        };
   2416      }
   2417      return option;
   2418    });
   2419 
   2420    // This is for accessibility to support making each item tabbable.
   2421    // We want to know which item is the first and which item
   2422    // is the last, so we can close the context menu accordingly.
   2423    linkMenuOptions[0].first = true;
   2424    linkMenuOptions[linkMenuOptions.length - 1].last = true;
   2425    return linkMenuOptions;
   2426  }
   2427  render() {
   2428    return /*#__PURE__*/external_React_default().createElement(ContextMenu, {
   2429      onUpdate: this.props.onUpdate,
   2430      onShow: this.props.onShow,
   2431      options: this.getOptions(),
   2432      keyboardAccess: this.props.keyboardAccess
   2433    });
   2434  }
   2435 }
   2436 const getState = state => ({
   2437  isPrivateBrowsingEnabled: state.Prefs.values.isPrivateBrowsingEnabled,
   2438  platform: state.Prefs.values.platform
   2439 });
   2440 const LinkMenu = (0,external_ReactRedux_namespaceObject.connect)(getState)(_LinkMenu);
   2441 ;// CONCATENATED MODULE: ./content-src/components/ContextMenu/ContextMenuButton.jsx
   2442 /* This Source Code Form is subject to the terms of the Mozilla Public
   2443 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   2444 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   2445 
   2446 
   2447 class ContextMenuButton extends (external_React_default()).PureComponent {
   2448  constructor(props) {
   2449    super(props);
   2450    this.state = {
   2451      showContextMenu: false,
   2452      contextMenuKeyboard: false
   2453    };
   2454    this.onClick = this.onClick.bind(this);
   2455    this.onKeyDown = this.onKeyDown.bind(this);
   2456    this.onUpdate = this.onUpdate.bind(this);
   2457  }
   2458  openContextMenu(isKeyBoard) {
   2459    if (this.props.onUpdate) {
   2460      this.props.onUpdate(true);
   2461    }
   2462    this.setState({
   2463      showContextMenu: true,
   2464      contextMenuKeyboard: isKeyBoard
   2465    });
   2466  }
   2467  onClick(event) {
   2468    event.preventDefault();
   2469    this.openContextMenu(false, event);
   2470  }
   2471  onKeyDown(event) {
   2472    if (event.key === "Enter" || event.key === " ") {
   2473      event.preventDefault();
   2474      this.openContextMenu(true, event);
   2475    }
   2476  }
   2477  onUpdate(showContextMenu) {
   2478    if (this.props.onUpdate) {
   2479      this.props.onUpdate(showContextMenu);
   2480    }
   2481    this.setState({
   2482      showContextMenu
   2483    });
   2484  }
   2485  render() {
   2486    const {
   2487      tooltipArgs,
   2488      tooltip,
   2489      children,
   2490      refFunction
   2491    } = this.props;
   2492    const {
   2493      showContextMenu,
   2494      contextMenuKeyboard
   2495    } = this.state;
   2496    return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement("button", {
   2497      "aria-haspopup": "menu",
   2498      "aria-expanded": showContextMenu,
   2499      "data-l10n-id": tooltip,
   2500      "data-l10n-args": tooltipArgs ? JSON.stringify(tooltipArgs) : null,
   2501      className: "context-menu-button icon",
   2502      onKeyDown: this.onKeyDown,
   2503      onClick: this.onClick,
   2504      ref: refFunction,
   2505      tabIndex: this.props.tabIndex || 0,
   2506      onFocus: this.props.onFocus
   2507    }), showContextMenu ? /*#__PURE__*/external_React_default().cloneElement(children, {
   2508      keyboardAccess: contextMenuKeyboard,
   2509      onUpdate: this.onUpdate
   2510    }) : null);
   2511  }
   2512 }
   2513 ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/DSLinkMenu/DSLinkMenu.jsx
   2514 /* This Source Code Form is subject to the terms of the Mozilla Public
   2515 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   2516 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   2517 
   2518 
   2519 
   2520 
   2521 
   2522 
   2523 class _DSLinkMenu extends (external_React_default()).PureComponent {
   2524  render() {
   2525    const {
   2526      index,
   2527      dispatch
   2528    } = this.props;
   2529    let TOP_STORIES_CONTEXT_MENU_OPTIONS;
   2530    const PREF_REPORT_ADS_ENABLED = "discoverystream.reportAds.enabled";
   2531    const prefs = this.props.Prefs.values;
   2532    const showAdsReporting = prefs[PREF_REPORT_ADS_ENABLED];
   2533    const isSpoc = this.props.card_type === "spoc";
   2534    if (isSpoc) {
   2535      TOP_STORIES_CONTEXT_MENU_OPTIONS = ["BlockUrl", ...(showAdsReporting ? ["ReportAd"] : []), "ManageSponsoredContent", "OurSponsorsAndYourPrivacy"];
   2536    } else {
   2537      TOP_STORIES_CONTEXT_MENU_OPTIONS = ["CheckBookmark", "Separator", "OpenInNewWindow", "OpenInPrivateWindow", "Separator", "BlockUrl", ...(this.props.section ? ["ReportContent"] : [])];
   2538    }
   2539    const type = this.props.type || "DISCOVERY_STREAM";
   2540    const title = this.props.title || this.props.source;
   2541    return /*#__PURE__*/external_React_default().createElement("div", {
   2542      className: "context-menu-position-container"
   2543    }, /*#__PURE__*/external_React_default().createElement(ContextMenuButton, {
   2544      tooltip: "newtab-menu-content-tooltip",
   2545      tooltipArgs: {
   2546        title
   2547      },
   2548      onUpdate: this.props.onMenuUpdate,
   2549      tabIndex: this.props.tabIndex
   2550    }, /*#__PURE__*/external_React_default().createElement(LinkMenu, {
   2551      dispatch: dispatch,
   2552      index: index,
   2553      source: type.toUpperCase(),
   2554      onShow: this.props.onMenuShow,
   2555      options: TOP_STORIES_CONTEXT_MENU_OPTIONS,
   2556      shouldSendImpressionStats: true,
   2557      userEvent: actionCreators.DiscoveryStreamUserEvent,
   2558      site: {
   2559        referrer: "https://getpocket.com/recommendations",
   2560        title: this.props.title,
   2561        type: this.props.type,
   2562        url: this.props.url,
   2563        guid: this.props.id,
   2564        pocket_id: this.props.pocket_id,
   2565        card_type: this.props.card_type,
   2566        shim: this.props.shim,
   2567        bookmarkGuid: this.props.bookmarkGuid,
   2568        flight_id: this.props.flightId,
   2569        tile_id: this.props.tile_id,
   2570        recommendation_id: this.props.recommendation_id,
   2571        corpus_item_id: this.props.corpus_item_id,
   2572        scheduled_corpus_item_id: this.props.scheduled_corpus_item_id,
   2573        firstVisibleTimestamp: this.props.firstVisibleTimestamp,
   2574        recommended_at: this.props.recommended_at,
   2575        received_rank: this.props.received_rank,
   2576        topic: this.props.topic,
   2577        position: index,
   2578        ...(this.props.format ? {
   2579          format: this.props.format
   2580        } : {}),
   2581        ...(this.props.section ? {
   2582          section: this.props.section,
   2583          section_position: this.props.section_position,
   2584          is_section_followed: this.props.is_section_followed
   2585        } : {})
   2586      }
   2587    })));
   2588  }
   2589 }
   2590 const DSLinkMenu = (0,external_ReactRedux_namespaceObject.connect)(state => ({
   2591  Prefs: state.Prefs
   2592 }))(_DSLinkMenu);
   2593 ;// CONCATENATED MODULE: ./content-src/lib/utils.jsx
   2594 /* This Source Code Form is subject to the terms of the Mozilla Public
   2595 * License, v. 2.0. If a copy of the MPL was not distributed with this
   2596 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
   2597 
   2598 const PREF_WEATHER_PLACEMENT = "weather.placement";
   2599 const PREF_DAILY_BRIEF_SECTIONID = "discoverystream.dailyBrief.sectionId";
   2600 const PREF_DAILY_BRIEF_ENABLED = "discoverystream.dailyBrief.enabled";
   2601 const PREF_STORIES_ENABLED = "feeds.section.topstories";
   2602 const PREF_SYSTEM_STORIES_ENABLED = "feeds.system.topstories";
   2603 
   2604 /**
   2605 * A custom react hook that sets up an IntersectionObserver to observe a single
   2606 * or list of elements and triggers a callback when the element comes into the viewport
   2607 * Note: The refs used should be an array type
   2608 *
   2609 * @function useIntersectionObserver
   2610 * @param {function} callback - The function to call when an element comes into the viewport
   2611 * @param {object} options - Options object passed to Intersection Observer:
   2612 * https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/IntersectionObserver#options
   2613 * @param {boolean} [isSingle = false] Boolean if the elements are an array or single element
   2614 *
   2615 * @returns {React.MutableRefObject} a ref containing an array of elements or single element
   2616 */
   2617 function useIntersectionObserver(callback, threshold = 0.3) {
   2618  const elementsRef = (0,external_React_namespaceObject.useRef)([]);
   2619  const triggeredElements = (0,external_React_namespaceObject.useRef)(new WeakSet());
   2620  (0,external_React_namespaceObject.useEffect)(() => {
   2621    const observer = new IntersectionObserver(entries => {
   2622      entries.forEach(entry => {
   2623        if (entry.isIntersecting && !triggeredElements.current.has(entry.target)) {
   2624          triggeredElements.current.add(entry.target);
   2625          callback(entry.target);
   2626          observer.unobserve(entry.target);
   2627        }
   2628      });
   2629    }, {
   2630      threshold
   2631    });
   2632    elementsRef.current.forEach(el => {
   2633      if (el && !triggeredElements.current.has(el)) {
   2634        observer.observe(el);
   2635      }
   2636    });
   2637 
   2638    // Cleanup function to disconnect observer on unmount
   2639    return () => observer.disconnect();
   2640  }, [callback, threshold]);
   2641  return elementsRef;
   2642 }
   2643 
   2644 /**
   2645 * Determines which column layout is active based on the screen width
   2646 *
   2647 * @param {number} screenWidth - The current window width (in pixels)
   2648 * @returns {string} The active column layout (e.g. "col-3", "col-2", "col-1")
   2649 */
   2650 function getActiveColumnLayout(screenWidth) {
   2651  const breakpoints = [{
   2652    min: 1374,
   2653    column: "col-4"
   2654  },
   2655  // $break-point-sections-variant
   2656  {
   2657    min: 1122,
   2658    column: "col-3"
   2659  },
   2660  // $break-point-widest
   2661  {
   2662    min: 724,
   2663    column: "col-2"
   2664  },
   2665  // $break-point-layout-variant
   2666  {
   2667    min: 0,
   2668    column: "col-1"
   2669  } // (default layout)
   2670  ];
   2671  return breakpoints.find(bp => screenWidth >= bp.min).column;
   2672 }
   2673 
   2674 /**
   2675 * Determines the active card size ("small", "medium", or "large") based on the screen width
   2676 * and class names applied to the card element at the time of an event (example: click)
   2677 *
   2678 * @param {number} screenWidth - The current window width (in pixels).
   2679 * @param {string | string[]} classNames - A string or array of class names applied to the sections card.
   2680 * @param {boolean[]} sectionsEnabled - If sections is not enabled, all cards are `medium-card`
   2681 * @param {number} flightId - Error ege case: This function should not be called on spocs, which have flightId
   2682 * @returns {"small-card" | "medium-card" | "large-card" | null} The active card type, or null if none is matched.
   2683 */
   2684 function getActiveCardSize(screenWidth, classNames, sectionsEnabled, flightId) {
   2685  // Only applies to sponsored content
   2686  if (flightId) {
   2687    return "spoc";
   2688  }
   2689 
   2690  // Default layout only supports `medium-card`
   2691  if (!sectionsEnabled) {
   2692    // Missing arguments
   2693    return "medium-card";
   2694  }
   2695 
   2696  // Return null if no values are available
   2697  if (!screenWidth || !classNames) {
   2698    // Missing arguments
   2699    return null;
   2700  }
   2701  const classList = classNames.split(" ");
   2702  const cardTypes = ["small", "medium", "large"];
   2703 
   2704  // Determine which column is active based on the current screen width
   2705  const currColumnCount = getActiveColumnLayout(screenWidth);
   2706 
   2707  // Match the card type for that column count
   2708  for (let type of cardTypes) {
   2709    const className = `${currColumnCount}-${type}`;
   2710    if (classList.includes(className)) {
   2711      // Special case: below $break-point-medium (610px), report `col-1-small` as medium
   2712      if (screenWidth < 610 && currColumnCount === "col-1" && type === "small") {
   2713        return "medium-card";
   2714      }
   2715      // Will be either "small-card", "medium-card", or "large-card"
   2716      return `${type}-card`;
   2717    }
   2718  }
   2719  return null;
   2720 }
   2721 const CONFETTI_VARS = ["--color-red-40", "--color-yellow-40", "--color-purple-40", "--color-blue-40", "--color-green-40"];
   2722 
   2723 /**
   2724 * Custom hook to animate a confetti burst.
   2725 *
   2726 * @param {number} count   Number of particles
   2727 * @param {number} spread  spread of confetti
   2728 * @returns {[React.RefObject<HTMLCanvasElement>, () => void]}
   2729 */
   2730 function useConfetti(count = 80, spread = Math.PI / 3) {
   2731  // avoid errors from about:home cache
   2732  const prefersReducedMotion = typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
   2733  let colors;
   2734  // if in abouthome cache, getComputedStyle will not be available
   2735  if (typeof getComputedStyle === "function") {
   2736    const styles = getComputedStyle(document.documentElement);
   2737    colors = CONFETTI_VARS.map(variable => styles.getPropertyValue(variable).trim());
   2738  } else {
   2739    colors = ["#fa5e75", "#de9600", "#c671eb", "#3f94ff", "#37b847"];
   2740  }
   2741  const canvasRef = (0,external_React_namespaceObject.useRef)(null);
   2742  const particlesRef = (0,external_React_namespaceObject.useRef)([]);
   2743  const animationFrameRef = (0,external_React_namespaceObject.useRef)(0);
   2744 
   2745  // initialize/reset pool
   2746  const initializeConfetti = (0,external_React_namespaceObject.useCallback)((width, height) => {
   2747    const centerX = width / 2;
   2748    const centerY = height;
   2749    const pool = particlesRef.current;
   2750 
   2751    // Create or overwrite each particle’s initial state
   2752    for (let i = 0; i < count; i++) {
   2753      const angle = Math.PI / 2 + (Math.random() - 0.5) * spread;
   2754      const cos = Math.cos(angle);
   2755      const sin = Math.sin(angle);
   2756      const color = colors[Math.floor(Math.random() * colors.length)];
   2757      pool[i] = {
   2758        x: centerX + (Math.random() - 0.5) * 40,
   2759        y: centerY,
   2760        cos,
   2761        sin,
   2762        velocity: Math.random() * 6 + 6,
   2763        gravity: 0.3,
   2764        decay: 0.96,
   2765        size: 8,
   2766        color,
   2767        life: 0,
   2768        maxLife: 100,
   2769        tilt: Math.random() * Math.PI * 2,
   2770        tiltSpeed: Math.random() * 0.2 + 0.05
   2771      };
   2772    }
   2773  }, [count, spread, colors]);
   2774 
   2775  // Core animation loop — updates physics & renders each frame
   2776  const animateParticles = (0,external_React_namespaceObject.useCallback)(canvas => {
   2777    const context = canvas.getContext("2d");
   2778    const {
   2779      width,
   2780      height
   2781    } = canvas;
   2782    const pool = particlesRef.current;
   2783 
   2784    // Clear the entire canvas each frame
   2785    context.clearRect(0, 0, width, height);
   2786    let anyAlive = false;
   2787    for (let particle of pool) {
   2788      if (particle.life < particle.maxLife) {
   2789        anyAlive = true;
   2790 
   2791        // update each particles physics: position, velocity decay, gravity, tilt, lifespan
   2792        particle.velocity *= particle.decay;
   2793        particle.x += particle.cos * particle.velocity;
   2794        particle.y -= particle.sin * particle.velocity;
   2795        particle.y += particle.gravity;
   2796        particle.tilt += particle.tiltSpeed;
   2797        particle.life += 1;
   2798      }
   2799 
   2800      // Draw: apply alpha, transform & draw a rotated, scaled square
   2801      const alphaValue = 1 - particle.life / particle.maxLife;
   2802      const scaleY = Math.sin(particle.tilt);
   2803      context.globalAlpha = alphaValue;
   2804      context.setTransform(1, 0, 0, 1, particle.x, particle.y);
   2805      context.rotate(Math.PI / 4);
   2806      context.scale(1, scaleY);
   2807      context.fillStyle = particle.color;
   2808      context.fillRect(-particle.size / 2, -particle.size / 2, particle.size, particle.size);
   2809 
   2810      // reset each particle
   2811      context.setTransform(1, 0, 0, 1, 0, 0);
   2812      context.globalAlpha = 1;
   2813    }
   2814    if (anyAlive) {
   2815      // continue the animation
   2816      animationFrameRef.current = requestAnimationFrame(() => {
   2817        animateParticles(canvas);
   2818      });
   2819    } else {
   2820      cancelAnimationFrame(animationFrameRef.current);
   2821      context.clearRect(0, 0, width, height);
   2822    }
   2823  }, []);
   2824 
   2825  // Resets and starts a new confetti animation
   2826  const fireConfetti = (0,external_React_namespaceObject.useCallback)(() => {
   2827    if (prefersReducedMotion) {
   2828      return;
   2829    }
   2830    const canvas = canvasRef?.current;
   2831    if (canvas) {
   2832      cancelAnimationFrame(animationFrameRef.current);
   2833      initializeConfetti(canvas.width, canvas.height);
   2834      animateParticles(canvas);
   2835    }
   2836  }, [initializeConfetti, animateParticles, prefersReducedMotion]);
   2837  return [canvasRef, fireConfetti];
   2838 }
   2839 function selectWeatherPlacement(state) {
   2840  const prefs = state.Prefs.values || {};
   2841 
   2842  // Intent: only placed in section if explicitly requested
   2843  const placementPref = prefs.trainhopConfig?.dailyBriefing?.placement || prefs[PREF_WEATHER_PLACEMENT];
   2844  if (placementPref === "header" || !placementPref) {
   2845    return "header";
   2846  }
   2847  const sections = state.DiscoveryStream.feeds.data["https://merino.services.mozilla.com/api/v1/curated-recommendations"]?.data.sections ?? [];
   2848  // check the following prefs to make sure weather is elligible to be placed in sections
   2849  // 1. The daily brieifng section must be availible and in the top position
   2850  // 2. That the daily briefing section has not been blocked
   2851  // 3. That reccomended stories are truned on
   2852  // Otherwise it should be placed in the header
   2853  const pocketEnabled = prefs[PREF_STORIES_ENABLED] && prefs[PREF_SYSTEM_STORIES_ENABLED];
   2854  const sectionPersonalization = state.DiscoveryStream?.sectionPersonalization || {};
   2855  const dailyBriefEnabled = prefs.trainhopConfig?.dailyBriefing?.enabled || prefs[PREF_DAILY_BRIEF_ENABLED];
   2856  const sectionId = prefs.trainhopConfig?.dailyBriefing?.sectionId || prefs[PREF_DAILY_BRIEF_SECTIONID];
   2857  const notBlocked = sectionId && !sectionPersonalization[sectionId]?.isBlocked;
   2858  let filteredSections = sections.filter(section => !sectionPersonalization[section.sectionKey]?.isBlocked);
   2859  const foundSection = filteredSections.find(section => section.sectionKey === sectionId);
   2860  const isTopSection = foundSection?.receivedRank === 0 || filteredSections.indexOf(foundSection) === 0;
   2861  const eligible = pocketEnabled && dailyBriefEnabled && sectionId && notBlocked && isTopSection;
   2862  return eligible ? "section" : "header";
   2863 }
   2864 
   2865 ;// CONCATENATED MODULE: ./content-src/components/TopSites/TopSitesConstants.mjs
   2866 /* This Source Code Form is subject to the terms of the Mozilla Public
   2867 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   2868 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   2869 
   2870 const TOP_SITES_SOURCE = "TOP_SITES";
   2871 const TOP_SITES_CONTEXT_MENU_OPTIONS = [
   2872  "CheckPinTopSite",
   2873  "EditTopSite",
   2874  "Separator",
   2875  "OpenInNewWindow",
   2876  "OpenInPrivateWindow",
   2877  "Separator",
   2878  "BlockUrl",
   2879  "DeleteUrl",
   2880 ];
   2881 const TOP_SITES_SPOC_CONTEXT_MENU_OPTIONS = [
   2882  "OpenInNewWindow",
   2883  "OpenInPrivateWindow",
   2884  "Separator",
   2885  "BlockUrl",
   2886  "ShowPrivacyInfo",
   2887 ];
   2888 const TOP_SITES_SPONSORED_POSITION_CONTEXT_MENU_OPTIONS = [
   2889  "OpenInNewWindow",
   2890  "OpenInPrivateWindow",
   2891  "Separator",
   2892  "BlockUrl",
   2893  "AboutSponsored",
   2894 ];
   2895 // the special top site for search shortcut experiment can only have the option to unpin (which removes) the topsite
   2896 const TOP_SITES_SEARCH_SHORTCUTS_CONTEXT_MENU_OPTIONS = [
   2897  "CheckPinTopSite",
   2898  "Separator",
   2899  "BlockUrl",
   2900 ];
   2901 // minimum size necessary to show a rich icon instead of a screenshot
   2902 const MIN_RICH_FAVICON_SIZE = 96;
   2903 // minimum size necessary to show any icon
   2904 const MIN_SMALL_FAVICON_SIZE = 16;
   2905 
   2906 ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamImpressionStats/ImpressionStats.jsx
   2907 /* This Source Code Form is subject to the terms of the Mozilla Public
   2908 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   2909 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   2910 
   2911 
   2912 
   2913 
   2914 
   2915 const VISIBLE = "visible";
   2916 const VISIBILITY_CHANGE_EVENT = "visibilitychange";
   2917 
   2918 // Per analytical requirement, we set the minimal intersection ratio to
   2919 // 0.5, and an impression is identified when the wrapped item has at least
   2920 // 50% visibility.
   2921 //
   2922 // This constant is exported for unit test
   2923 const INTERSECTION_RATIO = 0.5;
   2924 
   2925 /**
   2926 * Impression wrapper for Discovery Stream related React components.
   2927 *
   2928 * It makes use of the Intersection Observer API to detect the visibility,
   2929 * and relies on page visibility to ensure the impression is reported
   2930 * only when the component is visible on the page.
   2931 *
   2932 * Note:
   2933 *   * This wrapper used to be used either at the individual card level,
   2934 *     or by the card container components.
   2935 *     It is now only used for individual card level.
   2936 *   * Each impression will be sent only once as soon as the desired
   2937 *     visibility is detected
   2938 *   * Batching is not yet implemented, hence it might send multiple
   2939 *     impression pings separately
   2940 */
   2941 class ImpressionStats_ImpressionStats extends (external_React_default()).PureComponent {
   2942  // This checks if the given cards are the same as those in the last impression ping.
   2943  // If so, it should not send the same impression ping again.
   2944  _needsImpressionStats(cards) {
   2945    if (!this.impressionCardGuids || this.impressionCardGuids.length !== cards.length) {
   2946      return true;
   2947    }
   2948    for (let i = 0; i < cards.length; i++) {
   2949      if (cards[i].id !== this.impressionCardGuids[i]) {
   2950        return true;
   2951      }
   2952    }
   2953    return false;
   2954  }
   2955  _dispatchImpressionStats() {
   2956    const {
   2957      props
   2958    } = this;
   2959    const cards = props.rows;
   2960    if (this.props.flightId) {
   2961      this.props.dispatch(actionCreators.OnlyToMain({
   2962        type: actionTypes.DISCOVERY_STREAM_SPOC_IMPRESSION,
   2963        data: {
   2964          flightId: this.props.flightId
   2965        }
   2966      }));
   2967 
   2968      // Record sponsored topsites impressions if the source is `TOP_SITES_SOURCE`.
   2969      if (this.props.source === TOP_SITES_SOURCE) {
   2970        for (const card of cards) {
   2971          this.props.dispatch(actionCreators.OnlyToMain({
   2972            type: actionTypes.TOP_SITES_SPONSORED_IMPRESSION_STATS,
   2973            data: {
   2974              type: "impression",
   2975              tile_id: card.id,
   2976              source: "newtab",
   2977              advertiser: card.advertiser,
   2978              // Keep the 0-based position, can be adjusted by the telemetry
   2979              // sender if necessary.
   2980              position: card.pos,
   2981              attribution: card.attribution
   2982            }
   2983          }));
   2984        }
   2985      }
   2986    }
   2987    if (this._needsImpressionStats(cards)) {
   2988      props.dispatch(actionCreators.DiscoveryStreamImpressionStats({
   2989        source: props.source.toUpperCase(),
   2990        window_inner_width: window.innerWidth,
   2991        window_inner_height: window.innerHeight,
   2992        tiles: cards.map(link => ({
   2993          id: link.id,
   2994          pos: link.pos,
   2995          type: props.flightId ? "spoc" : "organic",
   2996          ...(link.shim ? {
   2997            shim: link.shim
   2998          } : {}),
   2999          recommendation_id: link.recommendation_id,
   3000          fetchTimestamp: link.fetchTimestamp,
   3001          corpus_item_id: link.corpus_item_id,
   3002          scheduled_corpus_item_id: link.scheduled_corpus_item_id,
   3003          recommended_at: link.recommended_at,
   3004          received_rank: link.received_rank,
   3005          topic: link.topic,
   3006          features: link.features,
   3007          attribution: link.attribution,
   3008          ...(link.format ? {
   3009            format: link.format
   3010          } : {
   3011            format: getActiveCardSize(window.innerWidth, link.class_names, link.section, link.flightId)
   3012          }),
   3013          ...(link.section ? {
   3014            section: link.section,
   3015            section_position: link.section_position,
   3016            is_section_followed: link.is_section_followed,
   3017            layout_name: link.sectionLayoutName
   3018          } : {})
   3019        })),
   3020        firstVisibleTimestamp: props.firstVisibleTimestamp
   3021      }));
   3022      this.impressionCardGuids = cards.map(link => link.id);
   3023    }
   3024  }
   3025 
   3026  // This checks if the given cards are the same as those in the last loaded content ping.
   3027  // If so, it should not send the same loaded content ping again.
   3028  _needsLoadedContent(cards) {
   3029    if (!this.loadedContentGuids || this.loadedContentGuids.length !== cards.length) {
   3030      return true;
   3031    }
   3032    for (let i = 0; i < cards.length; i++) {
   3033      if (cards[i].id !== this.loadedContentGuids[i]) {
   3034        return true;
   3035      }
   3036    }
   3037    return false;
   3038  }
   3039  _dispatchLoadedContent() {
   3040    const {
   3041      props
   3042    } = this;
   3043    const cards = props.rows;
   3044    if (this._needsLoadedContent(cards)) {
   3045      props.dispatch(actionCreators.DiscoveryStreamLoadedContent({
   3046        source: props.source.toUpperCase(),
   3047        tiles: cards.map(link => ({
   3048          id: link.id,
   3049          pos: link.pos
   3050        }))
   3051      }));
   3052      this.loadedContentGuids = cards.map(link => link.id);
   3053    }
   3054  }
   3055  setImpressionObserverOrAddListener() {
   3056    const {
   3057      props
   3058    } = this;
   3059    if (!props.dispatch) {
   3060      return;
   3061    }
   3062    if (props.document.visibilityState === VISIBLE) {
   3063      // Send the loaded content ping once the page is visible.
   3064      this._dispatchLoadedContent();
   3065      this.setImpressionObserver();
   3066    } else {
   3067      // We should only ever send the latest impression stats ping, so remove any
   3068      // older listeners.
   3069      if (this._onVisibilityChange) {
   3070        props.document.removeEventListener(VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
   3071      }
   3072      this._onVisibilityChange = () => {
   3073        if (props.document.visibilityState === VISIBLE) {
   3074          // Send the loaded content ping once the page is visible.
   3075          this._dispatchLoadedContent();
   3076          this.setImpressionObserver();
   3077          props.document.removeEventListener(VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
   3078        }
   3079      };
   3080      props.document.addEventListener(VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
   3081    }
   3082  }
   3083 
   3084  /**
   3085   * Set an impression observer for the wrapped component. It makes use of
   3086   * the Intersection Observer API to detect if the wrapped component is
   3087   * visible with a desired ratio, and only sends impression if that's the case.
   3088   *
   3089   * See more details about Intersection Observer API at:
   3090   * https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
   3091   */
   3092  setImpressionObserver() {
   3093    const {
   3094      props
   3095    } = this;
   3096    if (!props.rows.length) {
   3097      return;
   3098    }
   3099    this._handleIntersect = entries => {
   3100      if (entries.some(entry => entry.isIntersecting && entry.intersectionRatio >= INTERSECTION_RATIO)) {
   3101        this._dispatchImpressionStats();
   3102        this.impressionObserver.unobserve(this.refs.impression);
   3103      }
   3104    };
   3105    const options = {
   3106      threshold: INTERSECTION_RATIO
   3107    };
   3108    this.impressionObserver = new props.IntersectionObserver(this._handleIntersect, options);
   3109    this.impressionObserver.observe(this.refs.impression);
   3110  }
   3111  componentDidMount() {
   3112    if (this.props.rows.length) {
   3113      this.setImpressionObserverOrAddListener();
   3114    }
   3115  }
   3116  componentWillUnmount() {
   3117    if (this._handleIntersect && this.impressionObserver) {
   3118      this.impressionObserver.unobserve(this.refs.impression);
   3119    }
   3120    if (this._onVisibilityChange) {
   3121      this.props.document.removeEventListener(VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
   3122    }
   3123  }
   3124  render() {
   3125    return /*#__PURE__*/external_React_default().createElement("div", {
   3126      ref: "impression",
   3127      className: "impression-observer"
   3128    }, this.props.children);
   3129  }
   3130 }
   3131 ImpressionStats_ImpressionStats.defaultProps = {
   3132  IntersectionObserver: globalThis.IntersectionObserver,
   3133  document: globalThis.document,
   3134  rows: [],
   3135  source: ""
   3136 };
   3137 ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/SafeAnchor/SafeAnchor.jsx
   3138 function SafeAnchor_extends() { return SafeAnchor_extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, SafeAnchor_extends.apply(null, arguments); }
   3139 /* This Source Code Form is subject to the terms of the Mozilla Public
   3140 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   3141 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   3142 
   3143 
   3144 
   3145 class SafeAnchor extends (external_React_default()).PureComponent {
   3146  constructor(props) {
   3147    super(props);
   3148    this.onClick = this.onClick.bind(this);
   3149  }
   3150  onClick(event) {
   3151    // Use dispatch instead of normal link click behavior to include referrer
   3152    if (this.props.dispatch) {
   3153      event.preventDefault();
   3154      const {
   3155        altKey,
   3156        button,
   3157        ctrlKey,
   3158        metaKey,
   3159        shiftKey
   3160      } = event;
   3161      this.props.dispatch(actionCreators.OnlyToMain({
   3162        type: actionTypes.OPEN_LINK,
   3163        data: {
   3164          event: {
   3165            altKey,
   3166            button,
   3167            ctrlKey,
   3168            metaKey,
   3169            shiftKey
   3170          },
   3171          referrer: this.props.referrer || "https://getpocket.com/recommendations",
   3172          // Use the anchor's url, which could have been cleaned up
   3173          url: event.currentTarget.href,
   3174          is_sponsored: this.props.isSponsored
   3175        }
   3176      }));
   3177    }
   3178 
   3179    // Propagate event if there's a handler
   3180    if (this.props.onLinkClick) {
   3181      this.props.onLinkClick(event);
   3182    }
   3183  }
   3184  safeURI(url) {
   3185    let protocol = null;
   3186    try {
   3187      protocol = new URL(url).protocol;
   3188    } catch (e) {
   3189      return "";
   3190    }
   3191    const isAllowed = ["http:", "https:"].includes(protocol);
   3192    if (!isAllowed) {
   3193      console.warn(`${url} is not allowed for anchor targets.`); // eslint-disable-line no-console
   3194      return "";
   3195    }
   3196    return url;
   3197  }
   3198  render() {
   3199    const {
   3200      url,
   3201      className,
   3202      title,
   3203      isSponsored,
   3204      onFocus
   3205    } = this.props;
   3206    let anchor = /*#__PURE__*/external_React_default().createElement("a", SafeAnchor_extends({
   3207      href: this.safeURI(url),
   3208      title: title,
   3209      className: className,
   3210      onClick: this.onClick,
   3211      "data-is-sponsored-link": !!isSponsored
   3212    }, this.props.tabIndex === 0 || this.props.tabIndex ? {
   3213      ref: this.props.setRef,
   3214      tabIndex: this.props.tabIndex
   3215    } : {}, onFocus ? {
   3216      onFocus
   3217    } : {}), this.props.children);
   3218    return anchor;
   3219  }
   3220 }
   3221 ;// CONCATENATED MODULE: ./content-src/components/Card/types.mjs
   3222 /* This Source Code Form is subject to the terms of the Mozilla Public
   3223 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   3224 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   3225 
   3226 const cardContextTypes = {
   3227  history: {
   3228    fluentID: "newtab-label-visited",
   3229    icon: "history-item",
   3230  },
   3231  removedBookmark: {
   3232    fluentID: "newtab-label-removed-bookmark",
   3233    icon: "bookmark-removed",
   3234  },
   3235  bookmark: {
   3236    fluentID: "newtab-label-bookmarked",
   3237    icon: "bookmark-added",
   3238  },
   3239  trending: {
   3240    fluentID: "newtab-label-recommended",
   3241    icon: "trending",
   3242  },
   3243  pocket: {
   3244    fluentID: "newtab-label-saved",
   3245    icon: "pocket",
   3246  },
   3247  download: {
   3248    fluentID: "newtab-label-download",
   3249    icon: "download",
   3250  },
   3251 };
   3252 
   3253 ;// CONCATENATED MODULE: external "ReactTransitionGroup"
   3254 const external_ReactTransitionGroup_namespaceObject = ReactTransitionGroup;
   3255 ;// CONCATENATED MODULE: ./content-src/components/FluentOrText/FluentOrText.jsx
   3256 /* This Source Code Form is subject to the terms of the Mozilla Public
   3257 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   3258 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   3259 
   3260 
   3261 
   3262 /**
   3263 * Set text on a child element/component depending on if the message is already
   3264 * translated plain text or a fluent id with optional args.
   3265 */
   3266 class FluentOrText extends (external_React_default()).PureComponent {
   3267  render() {
   3268    // Ensure we have a single child to attach attributes
   3269    const {
   3270      children,
   3271      message
   3272    } = this.props;
   3273    const child = children ? external_React_default().Children.only(children) : /*#__PURE__*/external_React_default().createElement("span", null);
   3274 
   3275    // For a string message, just use it as the child's text
   3276    let grandChildren = message;
   3277    let extraProps;
   3278 
   3279    // Convert a message object to set desired fluent-dom attributes
   3280    if (typeof message === "object") {
   3281      const args = message.args || message.values;
   3282      extraProps = {
   3283        "data-l10n-args": args && JSON.stringify(args),
   3284        "data-l10n-id": message.id || message.string_id
   3285      };
   3286 
   3287      // Use original children potentially with data-l10n-name attributes
   3288      grandChildren = child.props.children;
   3289    }
   3290 
   3291    // Add the message to the child via fluent attributes or text node
   3292    return /*#__PURE__*/external_React_default().cloneElement(child, extraProps, grandChildren);
   3293  }
   3294 }
   3295 ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/DSContextFooter/DSContextFooter.jsx
   3296 /* This Source Code Form is subject to the terms of the Mozilla Public
   3297 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   3298 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   3299 
   3300 
   3301 // eslint-disable-next-line no-shadow
   3302 
   3303 
   3304 
   3305 
   3306 // Animation time is mirrored in DSContextFooter.scss
   3307 const ANIMATION_DURATION = 3000;
   3308 const DSMessageLabel = props => {
   3309  const {
   3310    context,
   3311    context_type,
   3312    mayHaveSectionsCards
   3313  } = props;
   3314  const {
   3315    icon,
   3316    fluentID
   3317  } = cardContextTypes[context_type] || {};
   3318  if (!context && context_type && !mayHaveSectionsCards) {
   3319    return /*#__PURE__*/external_React_default().createElement(external_ReactTransitionGroup_namespaceObject.TransitionGroup, {
   3320      component: null
   3321    }, /*#__PURE__*/external_React_default().createElement(external_ReactTransitionGroup_namespaceObject.CSSTransition, {
   3322      key: fluentID,
   3323      timeout: ANIMATION_DURATION,
   3324      classNames: "story-animate"
   3325    }, /*#__PURE__*/external_React_default().createElement(StatusMessage, {
   3326      icon: icon,
   3327      fluentID: fluentID
   3328    })));
   3329  }
   3330  return null;
   3331 };
   3332 const StatusMessage = ({
   3333  icon,
   3334  fluentID
   3335 }) => /*#__PURE__*/external_React_default().createElement("div", {
   3336  className: "status-message"
   3337 }, /*#__PURE__*/external_React_default().createElement("span", {
   3338  "aria-haspopup": "true",
   3339  className: `story-badge-icon icon icon-${icon}`
   3340 }), /*#__PURE__*/external_React_default().createElement("div", {
   3341  className: "story-context-label",
   3342  "data-l10n-id": fluentID
   3343 }));
   3344 const SponsorLabel = ({
   3345  sponsored_by_override,
   3346  sponsor,
   3347  context,
   3348  newSponsoredLabel
   3349 }) => {
   3350  const classList = `story-sponsored-label ${newSponsoredLabel || ""} clamp`;
   3351  // If override is not false or an empty string.
   3352  if (sponsored_by_override) {
   3353    return /*#__PURE__*/external_React_default().createElement("p", {
   3354      className: classList
   3355    }, sponsored_by_override);
   3356  } else if (sponsored_by_override === "") {
   3357    // We specifically want to display nothing if the server returns an empty string.
   3358    // So the server can turn off the label.
   3359    // This is to support the use cases where the sponsored context is displayed elsewhere.
   3360    return null;
   3361  } else if (sponsor) {
   3362    return /*#__PURE__*/external_React_default().createElement("p", {
   3363      className: classList
   3364    }, /*#__PURE__*/external_React_default().createElement(FluentOrText, {
   3365      message: {
   3366        id: `newtab-label-sponsored-by`,
   3367        values: {
   3368          sponsor
   3369        }
   3370      }
   3371    }));
   3372  } else if (context) {
   3373    return /*#__PURE__*/external_React_default().createElement("p", {
   3374      className: classList
   3375    }, context);
   3376  }
   3377  return null;
   3378 };
   3379 class DSContextFooter extends (external_React_default()).PureComponent {
   3380  render() {
   3381    const {
   3382      context,
   3383      context_type,
   3384      sponsor,
   3385      sponsored_by_override,
   3386      cta_button_variant,
   3387      source,
   3388      mayHaveSectionsCards
   3389    } = this.props;
   3390    const sponsorLabel = SponsorLabel({
   3391      sponsored_by_override,
   3392      sponsor,
   3393      context
   3394    });
   3395    const dsMessageLabel = DSMessageLabel({
   3396      context,
   3397      context_type,
   3398      mayHaveSectionsCards
   3399    });
   3400    if (cta_button_variant === "variant-a") {
   3401      return /*#__PURE__*/external_React_default().createElement("div", {
   3402        className: "story-footer"
   3403      }, /*#__PURE__*/external_React_default().createElement("button", {
   3404        "aria-hidden": "true",
   3405        className: "story-cta-button"
   3406      }, "Shop Now"), sponsorLabel);
   3407    }
   3408    if (cta_button_variant === "variant-b") {
   3409      return /*#__PURE__*/external_React_default().createElement("div", {
   3410        className: "story-footer"
   3411      }, sponsorLabel, /*#__PURE__*/external_React_default().createElement("span", {
   3412        className: "source clamp cta-footer-source"
   3413      }, source));
   3414    }
   3415    if (sponsorLabel || dsMessageLabel && context_type !== "pocket") {
   3416      return /*#__PURE__*/external_React_default().createElement("div", {
   3417        className: "story-footer"
   3418      }, sponsorLabel, dsMessageLabel);
   3419    }
   3420    return null;
   3421  }
   3422 }
   3423 const DSMessageFooter = props => {
   3424  const {
   3425    context,
   3426    context_type
   3427  } = props;
   3428  const dsMessageLabel = DSMessageLabel({
   3429    context,
   3430    context_type
   3431  });
   3432 
   3433  // This case is specific and already displayed to the user elsewhere.
   3434  if (!dsMessageLabel) {
   3435    return null;
   3436  }
   3437  return /*#__PURE__*/external_React_default().createElement("div", {
   3438    className: "story-footer"
   3439  }, dsMessageLabel);
   3440 };
   3441 ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/DSCard/DSCard.jsx
   3442 /* This Source Code Form is subject to the terms of the Mozilla Public
   3443 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   3444 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   3445 
   3446 
   3447 
   3448 
   3449 
   3450 
   3451 
   3452 
   3453 
   3454 
   3455 
   3456 const READING_WPM = 220;
   3457 const PREF_OHTTP_MERINO = "discoverystream.merino-provider.ohttp.enabled";
   3458 const PREF_OHTTP_UNIFIED_ADS = "unifiedAds.ohttp.enabled";
   3459 const DSCard_PREF_SECTIONS_ENABLED = "discoverystream.sections.enabled";
   3460 const PREF_FAVICONS_ENABLED = "discoverystream.publisherFavicon.enabled";
   3461 
   3462 /**
   3463 * READ TIME FROM WORD COUNT
   3464 *
   3465 * @param {int} wordCount number of words in an article
   3466 * @returns {int} number of words per minute in minutes
   3467 */
   3468 function readTimeFromWordCount(wordCount) {
   3469  if (!wordCount) {
   3470    return false;
   3471  }
   3472  return Math.ceil(parseInt(wordCount, 10) / READING_WPM);
   3473 }
   3474 const DSSource = ({
   3475  source,
   3476  timeToRead,
   3477  newSponsoredLabel,
   3478  context,
   3479  sponsor,
   3480  sponsored_by_override,
   3481  icon_src,
   3482  refinedCardsLayout
   3483 }) => {
   3484  // refinedCard styles will have a larger favicon size
   3485  const faviconSize = refinedCardsLayout ? 20 : 16;
   3486 
   3487  // First try to display sponsored label or time to read here.
   3488  if (newSponsoredLabel) {
   3489    // If we can display something for spocs, do so.
   3490    if (sponsored_by_override || sponsor || context) {
   3491      return /*#__PURE__*/external_React_default().createElement(SponsorLabel, {
   3492        context: context,
   3493        sponsor: sponsor,
   3494        sponsored_by_override: sponsored_by_override,
   3495        newSponsoredLabel: "new-sponsored-label"
   3496      });
   3497    }
   3498  }
   3499 
   3500  // If we are not a spoc, and can display a time to read value.
   3501  if (source && timeToRead) {
   3502    return /*#__PURE__*/external_React_default().createElement("p", {
   3503      className: "source clamp time-to-read"
   3504    }, /*#__PURE__*/external_React_default().createElement(FluentOrText, {
   3505      message: {
   3506        id: `newtab-label-source-read-time`,
   3507        values: {
   3508          source,
   3509          timeToRead
   3510        }
   3511      }
   3512    }));
   3513  }
   3514 
   3515  // Otherwise display a default source.
   3516  return /*#__PURE__*/external_React_default().createElement("div", {
   3517    className: "source-wrapper"
   3518  }, icon_src && /*#__PURE__*/external_React_default().createElement("img", {
   3519    src: icon_src,
   3520    height: faviconSize,
   3521    width: faviconSize,
   3522    alt: ""
   3523  }), /*#__PURE__*/external_React_default().createElement("p", {
   3524    className: "source clamp"
   3525  }, source));
   3526 };
   3527 const DefaultMeta = ({
   3528  source,
   3529  title,
   3530  excerpt,
   3531  timeToRead,
   3532  newSponsoredLabel,
   3533  context,
   3534  context_type,
   3535  sponsor,
   3536  sponsored_by_override,
   3537  ctaButtonVariant,
   3538  dispatch,
   3539  mayHaveSectionsCards,
   3540  format,
   3541  topic,
   3542  isSectionsCard,
   3543  showTopics,
   3544  icon_src,
   3545  refinedCardsLayout
   3546 }) => {
   3547  const shouldHaveFooterSection = isSectionsCard && showTopics;
   3548  return /*#__PURE__*/external_React_default().createElement("div", {
   3549    className: "meta"
   3550  }, /*#__PURE__*/external_React_default().createElement("div", {
   3551    className: "info-wrap"
   3552  }, ctaButtonVariant !== "variant-b" && format !== "rectangle" && !refinedCardsLayout && /*#__PURE__*/external_React_default().createElement(DSSource, {
   3553    source: source,
   3554    timeToRead: timeToRead,
   3555    newSponsoredLabel: newSponsoredLabel,
   3556    context: context,
   3557    sponsor: sponsor,
   3558    sponsored_by_override: sponsored_by_override,
   3559    icon_src: icon_src
   3560  }), /*#__PURE__*/external_React_default().createElement("h3", {
   3561    className: "title clamp"
   3562  }, format === "rectangle" ? "Sponsored" : title), format === "rectangle" ? /*#__PURE__*/external_React_default().createElement("p", {
   3563    className: "excerpt clamp"
   3564  }, "Sponsored content supports our mission to build a better web.") : excerpt && /*#__PURE__*/external_React_default().createElement("p", {
   3565    className: "excerpt clamp"
   3566  }, excerpt)), (shouldHaveFooterSection || refinedCardsLayout) && /*#__PURE__*/external_React_default().createElement("div", {
   3567    className: "sections-card-footer"
   3568  }, refinedCardsLayout && format !== "rectangle" && format !== "spoc" && /*#__PURE__*/external_React_default().createElement(DSSource, {
   3569    source: source,
   3570    timeToRead: timeToRead,
   3571    newSponsoredLabel: newSponsoredLabel,
   3572    context: context,
   3573    sponsor: sponsor,
   3574    sponsored_by_override: sponsored_by_override,
   3575    icon_src: icon_src,
   3576    refinedCardsLayout: refinedCardsLayout
   3577  }), showTopics && /*#__PURE__*/external_React_default().createElement("span", {
   3578    className: "ds-card-topic",
   3579    "data-l10n-id": `newtab-topic-label-${topic}`
   3580  })), !newSponsoredLabel && /*#__PURE__*/external_React_default().createElement(DSContextFooter, {
   3581    context_type: context_type,
   3582    context: context,
   3583    sponsor: sponsor,
   3584    sponsored_by_override: sponsored_by_override,
   3585    cta_button_variant: ctaButtonVariant,
   3586    source: source,
   3587    dispatch: dispatch,
   3588    mayHaveSectionsCards: mayHaveSectionsCards
   3589  }), newSponsoredLabel && /*#__PURE__*/external_React_default().createElement(DSMessageFooter, {
   3590    context_type: context_type,
   3591    context: null
   3592  }));
   3593 };
   3594 class _DSCard extends (external_React_default()).PureComponent {
   3595  constructor(props) {
   3596    super(props);
   3597    this.onLinkClick = this.onLinkClick.bind(this);
   3598    this.doesLinkTopicMatchSelectedTopic = this.doesLinkTopicMatchSelectedTopic.bind(this);
   3599    this.onMenuUpdate = this.onMenuUpdate.bind(this);
   3600    this.onMenuShow = this.onMenuShow.bind(this);
   3601    const refinedCardsLayout = this.props.Prefs.values["discoverystream.refinedCardsLayout.enabled"];
   3602    this.setContextMenuButtonHostRef = element => {
   3603      this.contextMenuButtonHostElement = element;
   3604    };
   3605    this.setPlaceholderRef = element => {
   3606      this.placeholderElement = element;
   3607    };
   3608    this.state = {
   3609      isSeen: false
   3610    };
   3611 
   3612    // If this is for the about:home startup cache, then we always want
   3613    // to render the DSCard, regardless of whether or not its been seen.
   3614    if (props.App.isForStartupCache.App) {
   3615      this.state.isSeen = true;
   3616    }
   3617 
   3618    // We want to choose the optimal thumbnail for the underlying DSImage, but
   3619    // want to do it in a performant way. The breakpoints used in the
   3620    // CSS of the page are, unfortuntely, not easy to retrieve without
   3621    // causing a style flush. To avoid that, we hardcode them here.
   3622    //
   3623    // The values chosen here were the dimensions of the card thumbnails as
   3624    // computed by getBoundingClientRect() for each type of viewport width
   3625    // across both high-density and normal-density displays.
   3626    this.standardCardImageSizes = [{
   3627      mediaMatcher: "default",
   3628      width: 296,
   3629      height: refinedCardsLayout ? 160 : 148
   3630    }];
   3631    this.listCardImageSizes = [{
   3632      mediaMatcher: "(min-width: 1122px)",
   3633      width: 75,
   3634      height: 75
   3635    }, {
   3636      mediaMatcher: "default",
   3637      width: 50,
   3638      height: 50
   3639    }];
   3640    this.sectionsCardImagesSizes = {
   3641      small: {
   3642        width: 110,
   3643        height: 117
   3644      },
   3645      medium: {
   3646        width: 300,
   3647        height: refinedCardsLayout ? 160 : 150
   3648      },
   3649      large: {
   3650        width: 190,
   3651        height: 250
   3652      }
   3653    };
   3654    this.sectionsColumnMediaMatcher = {
   3655      1: "default",
   3656      2: "(min-width: 724px)",
   3657      3: "(min-width: 1122px)",
   3658      4: "(min-width: 1390px)"
   3659    };
   3660  }
   3661  getSectionImageSize(column, size) {
   3662    const cardImageSize = {
   3663      mediaMatcher: this.sectionsColumnMediaMatcher[column],
   3664      width: this.sectionsCardImagesSizes[size].width,
   3665      height: this.sectionsCardImagesSizes[size].height
   3666    };
   3667    return cardImageSize;
   3668  }
   3669  doesLinkTopicMatchSelectedTopic() {
   3670    // Edge case for clicking on a card when topic selections have not be set
   3671    if (!this.props.selectedTopics) {
   3672      return "not-set";
   3673    }
   3674 
   3675    // Edge case the topic of the card is not one of the available topics
   3676    if (!this.props.availableTopics.includes(this.props.topic)) {
   3677      return "topic-not-selectable";
   3678    }
   3679    if (this.props.selectedTopics.includes(this.props.topic)) {
   3680      return "true";
   3681    }
   3682    return "false";
   3683  }
   3684  onLinkClick() {
   3685    const matchesSelectedTopic = this.doesLinkTopicMatchSelectedTopic();
   3686    if (this.props.dispatch) {
   3687      this.props.dispatch(actionCreators.DiscoveryStreamUserEvent({
   3688        event: "CLICK",
   3689        source: this.props.type.toUpperCase(),
   3690        action_position: this.props.pos,
   3691        value: {
   3692          event_source: "card",
   3693          card_type: this.props.flightId ? "spoc" : "organic",
   3694          recommendation_id: this.props.recommendation_id,
   3695          tile_id: this.props.id,
   3696          ...(this.props.shim && this.props.shim.click ? {
   3697            shim: this.props.shim.click
   3698          } : {}),
   3699          fetchTimestamp: this.props.fetchTimestamp,
   3700          firstVisibleTimestamp: this.props.firstVisibleTimestamp,
   3701          corpus_item_id: this.props.corpus_item_id,
   3702          scheduled_corpus_item_id: this.props.scheduled_corpus_item_id,
   3703          recommended_at: this.props.recommended_at,
   3704          received_rank: this.props.received_rank,
   3705          topic: this.props.topic,
   3706          features: this.props.features,
   3707          matches_selected_topic: matchesSelectedTopic,
   3708          selected_topics: this.props.selectedTopics,
   3709          attribution: this.props.attribution,
   3710          ...(this.props.format ? {
   3711            format: this.props.format
   3712          } : {
   3713            format: getActiveCardSize(window.innerWidth, this.props.sectionsClassNames, this.props.section, this.props.flightId)
   3714          }),
   3715          ...(this.props.section ? {
   3716            section: this.props.section,
   3717            section_position: this.props.sectionPosition,
   3718            is_section_followed: this.props.sectionFollowed,
   3719            layout_name: this.props.sectionLayoutName
   3720          } : {})
   3721        }
   3722      }));
   3723      this.props.dispatch(actionCreators.ImpressionStats({
   3724        source: this.props.type.toUpperCase(),
   3725        click: 0,
   3726        window_inner_width: this.props.windowObj.innerWidth,
   3727        window_inner_height: this.props.windowObj.innerHeight,
   3728        tiles: [{
   3729          id: this.props.id,
   3730          pos: this.props.pos,
   3731          ...(this.props.shim && this.props.shim.click ? {
   3732            shim: this.props.shim.click
   3733          } : {}),
   3734          type: this.props.flightId ? "spoc" : "organic",
   3735          recommendation_id: this.props.recommendation_id,
   3736          topic: this.props.topic,
   3737          selected_topics: this.props.selectedTopics,
   3738          ...(this.props.format ? {
   3739            format: this.props.format
   3740          } : {
   3741            format: getActiveCardSize(window.innerWidth, this.props.sectionsClassNames, this.props.section, this.props.flightId)
   3742          }),
   3743          ...(this.props.section ? {
   3744            section: this.props.section,
   3745            section_position: this.props.sectionPosition,
   3746            is_section_followed: this.props.sectionFollowed
   3747          } : {})
   3748        }]
   3749      }));
   3750    }
   3751  }
   3752  onMenuUpdate(showContextMenu) {
   3753    if (!showContextMenu) {
   3754      const dsLinkMenuHostDiv = this.contextMenuButtonHostElement;
   3755      if (dsLinkMenuHostDiv) {
   3756        dsLinkMenuHostDiv.classList.remove("active", "last-item");
   3757      }
   3758    }
   3759  }
   3760  async onMenuShow() {
   3761    const dsLinkMenuHostDiv = this.contextMenuButtonHostElement;
   3762    if (dsLinkMenuHostDiv) {
   3763      // Force translation so we can be sure it's ready before measuring.
   3764      await this.props.windowObj.document.l10n.translateFragment(dsLinkMenuHostDiv);
   3765      if (this.props.windowObj.scrollMaxX > 0) {
   3766        dsLinkMenuHostDiv.classList.add("last-item");
   3767      }
   3768      dsLinkMenuHostDiv.classList.add("active");
   3769    }
   3770  }
   3771  onSeen(entries) {
   3772    if (this.state) {
   3773      const entry = entries.find(e => e.isIntersecting);
   3774      if (entry) {
   3775        if (this.placeholderElement) {
   3776          this.observer.unobserve(this.placeholderElement);
   3777        }
   3778 
   3779        // Stop observing since element has been seen
   3780        this.setState({
   3781          isSeen: true
   3782        });
   3783      }
   3784    }
   3785  }
   3786  onIdleCallback() {
   3787    if (!this.state.isSeen) {
   3788      // To improve responsiveness without impacting performance,
   3789      // we start rendering stories on idle.
   3790      // To reduce the number of requests for secure OHTTP images,
   3791      // we skip idle-time loading.
   3792      if (!this.secureImage) {
   3793        if (this.observer && this.placeholderElement) {
   3794          this.observer.unobserve(this.placeholderElement);
   3795        }
   3796        this.setState({
   3797          isSeen: true
   3798        });
   3799      }
   3800    }
   3801  }
   3802  componentDidMount() {
   3803    this.idleCallbackId = this.props.windowObj.requestIdleCallback(this.onIdleCallback.bind(this));
   3804    if (this.placeholderElement) {
   3805      this.observer = new IntersectionObserver(this.onSeen.bind(this));
   3806      this.observer.observe(this.placeholderElement);
   3807    }
   3808  }
   3809  componentWillUnmount() {
   3810    // Remove observer on unmount
   3811    if (this.observer && this.placeholderElement) {
   3812      this.observer.unobserve(this.placeholderElement);
   3813    }
   3814    if (this.idleCallbackId) {
   3815      this.props.windowObj.cancelIdleCallback(this.idleCallbackId);
   3816    }
   3817  }
   3818 
   3819  // Wraps the image URL with the moz-cached-ohttp:// protocol.
   3820  // This enables Firefox to load resources over Oblivious HTTP (OHTTP),
   3821  // providing privacy-preserving resource loading.
   3822  // Applied only when inferred personalization is enabled.
   3823  // See: https://firefox-source-docs.mozilla.org/browser/components/mozcachedohttp/docs/index.html
   3824  secureImageURL(url) {
   3825    return `moz-cached-ohttp://newtab-image/?url=${encodeURIComponent(url)}`;
   3826  }
   3827  getRawImageSrc() {
   3828    let rawImageSrc = "";
   3829    // There is no point in fetching images for startup cache.
   3830    if (!this.props.App.isForStartupCache.App) {
   3831      rawImageSrc = this.props.raw_image_src;
   3832    }
   3833    return rawImageSrc;
   3834  }
   3835  getFaviconSrc() {
   3836    let faviconSrc = "";
   3837    const faviconEnabled = this.props.Prefs.values[PREF_FAVICONS_ENABLED];
   3838    // There is no point in fetching favicons for startup cache.
   3839    if (!this.props.App.isForStartupCache.App && faviconEnabled && this.props.icon_src) {
   3840      faviconSrc = this.props.icon_src;
   3841      if (this.secureImage) {
   3842        faviconSrc = this.secureImageURL(this.props.icon_src);
   3843      }
   3844    }
   3845    return faviconSrc;
   3846  }
   3847  get secureImage() {
   3848    const {
   3849      Prefs,
   3850      flightId
   3851    } = this.props;
   3852    let ohttpEnabled = false;
   3853    if (flightId) {
   3854      ohttpEnabled = Prefs.values[PREF_OHTTP_UNIFIED_ADS];
   3855    } else {
   3856      ohttpEnabled = Prefs.values[PREF_OHTTP_MERINO];
   3857    }
   3858    const ohttpImagesEnabled = Prefs.values.ohttpImagesConfig?.enabled;
   3859    const includeTopStoriesSection = Prefs.values.ohttpImagesConfig?.includeTopStoriesSection;
   3860    const nonPersonalizedSections = ["top_stories_section"];
   3861    const sectionPersonalized = !nonPersonalizedSections.includes(this.props.section) || includeTopStoriesSection;
   3862    const secureImage = ohttpImagesEnabled && ohttpEnabled && sectionPersonalized;
   3863    return secureImage;
   3864  }
   3865  renderImage({
   3866    sizes = [],
   3867    classNames = ""
   3868  } = {}) {
   3869    const {
   3870      Prefs
   3871    } = this.props;
   3872    const rawImageSrc = this.getRawImageSrc();
   3873    const smartCrop = Prefs.values["images.smart"];
   3874    return /*#__PURE__*/external_React_default().createElement(DSImage, {
   3875      extraClassNames: `img ${classNames}`,
   3876      source: this.props.image_src,
   3877      rawSource: rawImageSrc,
   3878      sizes: sizes,
   3879      url: this.props.url,
   3880      title: this.props.title,
   3881      isRecentSave: this.props.isRecentSave,
   3882      alt_text: this.props.alt_text,
   3883      smartCrop: smartCrop,
   3884      secureImage: this.secureImage
   3885    });
   3886  }
   3887  renderSectionCardImages() {
   3888    const {
   3889      sectionsCardImageSizes
   3890    } = this.props;
   3891    const columns = ["1", "2", "3", "4"];
   3892    const images = [];
   3893    for (const column of columns) {
   3894      const size = sectionsCardImageSizes[column];
   3895      const sizes = [this.getSectionImageSize(column, size)];
   3896      const image = this.renderImage({
   3897        sizes,
   3898        classNames: `image-${column}`
   3899      });
   3900      images.push(image);
   3901    }
   3902    return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, images);
   3903  }
   3904  render() {
   3905    const {
   3906      isRecentSave,
   3907      DiscoveryStream,
   3908      Prefs,
   3909      mayHaveSectionsCards,
   3910      format
   3911    } = this.props;
   3912    const refinedCardsLayout = Prefs.values["discoverystream.refinedCardsLayout.enabled"];
   3913    const refinedCardsClassName = refinedCardsLayout ? `refined-cards` : ``;
   3914    if (this.props.placeholder || !this.state.isSeen) {
   3915      // placeholder-seen is used to ensure the loading animation is only used if the card is visible.
   3916      const placeholderClassName = this.state.isSeen ? `placeholder-seen` : ``;
   3917      let placeholderElements = /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement("div", {
   3918        className: "placeholder-image placeholder-fill"
   3919      }), /*#__PURE__*/external_React_default().createElement("div", {
   3920        className: "placeholder-label placeholder-fill"
   3921      }), /*#__PURE__*/external_React_default().createElement("div", {
   3922        className: "placeholder-header placeholder-fill"
   3923      }), /*#__PURE__*/external_React_default().createElement("div", {
   3924        className: "placeholder-description placeholder-fill"
   3925      }));
   3926      if (refinedCardsLayout) {
   3927        placeholderElements = /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement("div", {
   3928          className: "placeholder-image placeholder-fill"
   3929        }), /*#__PURE__*/external_React_default().createElement("div", {
   3930          className: "placeholder-description placeholder-fill"
   3931        }), /*#__PURE__*/external_React_default().createElement("div", {
   3932          className: "placeholder-header placeholder-fill"
   3933        }));
   3934      }
   3935      return /*#__PURE__*/external_React_default().createElement("div", {
   3936        className: `ds-card placeholder ${placeholderClassName} ${refinedCardsClassName}`,
   3937        ref: this.setPlaceholderRef
   3938      }, placeholderElements);
   3939    }
   3940    let source = this.props.source || this.props.publisher;
   3941    if (!source) {
   3942      try {
   3943        source = new URL(this.props.url).hostname;
   3944      } catch (e) {}
   3945    }
   3946    const {
   3947      hideDescriptions,
   3948      compactImages,
   3949      imageGradient,
   3950      newSponsoredLabel,
   3951      titleLines = 3,
   3952      descLines = 3,
   3953      readTime: displayReadTime
   3954    } = DiscoveryStream;
   3955    const sectionsEnabled = Prefs.values[DSCard_PREF_SECTIONS_ENABLED];
   3956    // Refined cards have their own excerpt hiding logic.
   3957    // We can ignore hideDescriptions if we are in sections and refined cards.
   3958    const excerpt = !hideDescriptions || sectionsEnabled && refinedCardsLayout ? this.props.excerpt : "";
   3959    let timeToRead;
   3960    if (displayReadTime) {
   3961      timeToRead = this.props.time_to_read || readTimeFromWordCount(this.props.word_count);
   3962    }
   3963    const ctaButtonEnabled = this.props.ctaButtonSponsors?.includes(this.props.sponsor?.toLowerCase());
   3964    let ctaButtonVariant = "";
   3965    if (ctaButtonEnabled) {
   3966      ctaButtonVariant = this.props.ctaButtonVariant;
   3967    }
   3968    let ctaButtonVariantClassName = ctaButtonVariant;
   3969    const ctaButtonClassName = ctaButtonEnabled ? `ds-card-cta-button` : ``;
   3970    const compactImagesClassName = compactImages ? `ds-card-compact-image` : ``;
   3971    const imageGradientClassName = imageGradient ? `ds-card-image-gradient` : ``;
   3972    const sectionsCardsClassName = [mayHaveSectionsCards ? `sections-card-ui` : ``, this.props.sectionsClassNames].join(" ");
   3973    const titleLinesName = `ds-card-title-lines-${titleLines}`;
   3974    const descLinesClassName = `ds-card-desc-lines-${descLines}`;
   3975    const isMediumRectangle = format === "rectangle";
   3976    const spocFormatClassName = isMediumRectangle ? `ds-spoc-rectangle` : ``;
   3977    const faviconSrc = this.getFaviconSrc();
   3978    let images = this.renderImage({
   3979      sizes: this.standardCardImageSizes
   3980    });
   3981    if (isMediumRectangle) {
   3982      images = this.renderImage();
   3983    } else if (sectionsEnabled) {
   3984      images = this.renderSectionCardImages();
   3985    }
   3986    return /*#__PURE__*/external_React_default().createElement("article", {
   3987      className: `ds-card ${sectionsCardsClassName} ${compactImagesClassName} ${imageGradientClassName} ${titleLinesName} ${descLinesClassName} ${spocFormatClassName} ${ctaButtonClassName} ${ctaButtonVariantClassName} ${refinedCardsClassName}`,
   3988      ref: this.setContextMenuButtonHostRef,
   3989      "data-position-one": this.props["data-position-one"],
   3990      "data-position-two": this.props["data-position-one"],
   3991      "data-position-three": this.props["data-position-one"],
   3992      "data-position-four": this.props["data-position-one"]
   3993    }, /*#__PURE__*/external_React_default().createElement(SafeAnchor, {
   3994      className: "ds-card-link",
   3995      dispatch: this.props.dispatch,
   3996      onLinkClick: !this.props.placeholder ? this.onLinkClick : undefined,
   3997      url: this.props.url,
   3998      title: this.props.title,
   3999      isSponsored: !!this.props.flightId,
   4000      tabIndex: this.props.tabIndex,
   4001      onFocus: this.props.onFocus
   4002    }, this.props.showTopics && !this.props.mayHaveSectionsCards && this.props.topic && !refinedCardsLayout && /*#__PURE__*/external_React_default().createElement("span", {
   4003      className: "ds-card-topic",
   4004      "data-l10n-id": `newtab-topic-label-${this.props.topic}`
   4005    }), /*#__PURE__*/external_React_default().createElement("div", {
   4006      className: "img-wrapper"
   4007    }, images), /*#__PURE__*/external_React_default().createElement(ImpressionStats_ImpressionStats, {
   4008      flightId: this.props.flightId,
   4009      rows: [{
   4010        id: this.props.id,
   4011        pos: this.props.pos,
   4012        ...(this.props.shim && this.props.shim.impression ? {
   4013          shim: this.props.shim.impression
   4014        } : {}),
   4015        recommendation_id: this.props.recommendation_id,
   4016        fetchTimestamp: this.props.fetchTimestamp,
   4017        corpus_item_id: this.props.corpus_item_id,
   4018        scheduled_corpus_item_id: this.props.scheduled_corpus_item_id,
   4019        recommended_at: this.props.recommended_at,
   4020        received_rank: this.props.received_rank,
   4021        topic: this.props.topic,
   4022        features: this.props.features,
   4023        ...(format ? {
   4024          format
   4025        } : {}),
   4026        category: this.props.category,
   4027        attribution: this.props.attribution,
   4028        ...(this.props.section ? {
   4029          section: this.props.section,
   4030          section_position: this.props.sectionPosition,
   4031          is_section_followed: this.props.sectionFollowed,
   4032          sectionLayoutName: this.props.sectionLayoutName
   4033        } : {}),
   4034        ...(!format && this.props.section ?
   4035        // Note: sectionsCardsClassName is passed to ImpressionStats.jsx in order to calculate format
   4036        {
   4037          class_names: sectionsCardsClassName
   4038        } : {})
   4039      }],
   4040      dispatch: this.props.dispatch,
   4041      source: this.props.type,
   4042      firstVisibleTimestamp: this.props.firstVisibleTimestamp
   4043    }), ctaButtonVariant === "variant-b" && /*#__PURE__*/external_React_default().createElement("div", {
   4044      className: "cta-header"
   4045    }, "Shop Now"), /*#__PURE__*/external_React_default().createElement(DefaultMeta, {
   4046      source: source,
   4047      title: this.props.title,
   4048      excerpt: excerpt,
   4049      newSponsoredLabel: newSponsoredLabel,
   4050      timeToRead: timeToRead,
   4051      context: this.props.context,
   4052      context_type: this.props.context_type,
   4053      sponsor: this.props.sponsor,
   4054      sponsored_by_override: this.props.sponsored_by_override,
   4055      ctaButtonVariant: ctaButtonVariant,
   4056      dispatch: this.props.dispatch,
   4057      mayHaveSectionsCards: this.props.mayHaveSectionsCards,
   4058      state: this.state,
   4059      showTopics: !refinedCardsLayout && this.props.showTopics,
   4060      isSectionsCard: this.props.mayHaveSectionsCards && this.props.topic,
   4061      format: format,
   4062      topic: this.props.topic,
   4063      icon_src: faviconSrc,
   4064      refinedCardsLayout: refinedCardsLayout,
   4065      tabIndex: this.props.tabIndex
   4066    })), /*#__PURE__*/external_React_default().createElement("div", {
   4067      className: "card-stp-button-hover-background"
   4068    }, /*#__PURE__*/external_React_default().createElement("div", {
   4069      className: "card-stp-button-position-wrapper"
   4070    }, /*#__PURE__*/external_React_default().createElement(DSLinkMenu, {
   4071      id: this.props.id,
   4072      index: this.props.pos,
   4073      dispatch: this.props.dispatch,
   4074      url: this.props.url,
   4075      title: this.props.title,
   4076      source: source,
   4077      type: this.props.type,
   4078      card_type: this.props.flightId ? "spoc" : "organic",
   4079      pocket_id: this.props.pocket_id,
   4080      shim: this.props.shim,
   4081      bookmarkGuid: this.props.bookmarkGuid,
   4082      flightId: this.props.flightId,
   4083      showPrivacyInfo: !!this.props.flightId,
   4084      onMenuUpdate: this.onMenuUpdate,
   4085      onMenuShow: this.onMenuShow,
   4086      isRecentSave: isRecentSave,
   4087      recommendation_id: this.props.recommendation_id,
   4088      tile_id: this.props.id,
   4089      block_key: this.props.id,
   4090      corpus_item_id: this.props.corpus_item_id,
   4091      scheduled_corpus_item_id: this.props.scheduled_corpus_item_id,
   4092      recommended_at: this.props.recommended_at,
   4093      received_rank: this.props.received_rank,
   4094      section: this.props.section,
   4095      section_position: this.props.sectionPosition,
   4096      is_section_followed: this.props.sectionFollowed,
   4097      fetchTimestamp: this.props.fetchTimestamp,
   4098      firstVisibleTimestamp: this.props.firstVisibleTimestamp,
   4099      format: format ? format : getActiveCardSize(window.innerWidth, this.props.sectionsClassNames, this.props.section, this.props.flightId),
   4100      isSectionsCard: this.props.mayHaveSectionsCards,
   4101      topic: this.props.topic,
   4102      selected_topics: this.props.selected_topics,
   4103      tabIndex: this.props.tabIndex
   4104    }))));
   4105  }
   4106 }
   4107 _DSCard.defaultProps = {
   4108  windowObj: window // Added to support unit tests
   4109 };
   4110 const DSCard = (0,external_ReactRedux_namespaceObject.connect)(state => ({
   4111  App: state.App,
   4112  DiscoveryStream: state.DiscoveryStream,
   4113  Prefs: state.Prefs
   4114 }))(_DSCard);
   4115 const PlaceholderDSCard = () => /*#__PURE__*/external_React_default().createElement(DSCard, {
   4116  placeholder: true
   4117 });
   4118 ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/DSEmptyState/DSEmptyState.jsx
   4119 /* This Source Code Form is subject to the terms of the Mozilla Public
   4120 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   4121 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   4122 
   4123 
   4124 
   4125 class DSEmptyState extends (external_React_default()).PureComponent {
   4126  constructor(props) {
   4127    super(props);
   4128    this.onReset = this.onReset.bind(this);
   4129    this.state = {};
   4130  }
   4131  componentWillUnmount() {
   4132    if (this.timeout) {
   4133      clearTimeout(this.timeout);
   4134    }
   4135  }
   4136  onReset() {
   4137    if (this.props.dispatch && this.props.feed) {
   4138      const {
   4139        feed
   4140      } = this.props;
   4141      const {
   4142        url
   4143      } = feed;
   4144      this.props.dispatch({
   4145        type: actionTypes.DISCOVERY_STREAM_FEED_UPDATE,
   4146        data: {
   4147          feed: {
   4148            ...feed,
   4149            data: {
   4150              ...feed.data,
   4151              status: "waiting"
   4152            }
   4153          },
   4154          url
   4155        }
   4156      });
   4157      this.setState({
   4158        waiting: true
   4159      });
   4160      this.timeout = setTimeout(() => {
   4161        this.timeout = null;
   4162        this.setState({
   4163          waiting: false
   4164        });
   4165      }, 300);
   4166      this.props.dispatch(actionCreators.OnlyToMain({
   4167        type: actionTypes.DISCOVERY_STREAM_RETRY_FEED,
   4168        data: {
   4169          feed
   4170        }
   4171      }));
   4172    }
   4173  }
   4174  renderButton() {
   4175    if (this.props.status === "waiting" || this.state.waiting) {
   4176      return /*#__PURE__*/external_React_default().createElement("button", {
   4177        className: "try-again-button waiting",
   4178        "data-l10n-id": "newtab-discovery-empty-section-topstories-loading"
   4179      });
   4180    }
   4181    return /*#__PURE__*/external_React_default().createElement("button", {
   4182      className: "try-again-button",
   4183      onClick: this.onReset,
   4184      "data-l10n-id": "newtab-discovery-empty-section-topstories-try-again-button"
   4185    });
   4186  }
   4187  renderState() {
   4188    if (this.props.status === "waiting" || this.props.status === "failed") {
   4189      return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement("h2", {
   4190        "data-l10n-id": "newtab-discovery-empty-section-topstories-timed-out"
   4191      }), this.renderButton());
   4192    }
   4193    return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement("h2", {
   4194      "data-l10n-id": "newtab-discovery-empty-section-topstories-header"
   4195    }), /*#__PURE__*/external_React_default().createElement("p", {
   4196      "data-l10n-id": "newtab-discovery-empty-section-topstories-content"
   4197    }));
   4198  }
   4199  render() {
   4200    return /*#__PURE__*/external_React_default().createElement("div", {
   4201      className: "section-empty-state"
   4202    }, /*#__PURE__*/external_React_default().createElement("div", {
   4203      className: "empty-state-message"
   4204    }, this.renderState()));
   4205  }
   4206 }
   4207 ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/TopicsWidget/TopicsWidget.jsx
   4208 /* This Source Code Form is subject to the terms of the Mozilla Public
   4209 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   4210 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   4211 
   4212 
   4213 
   4214 
   4215 
   4216 
   4217 function _TopicsWidget(props) {
   4218  const {
   4219    id,
   4220    source,
   4221    position,
   4222    DiscoveryStream,
   4223    dispatch
   4224  } = props;
   4225  const {
   4226    utmCampaign,
   4227    utmContent,
   4228    utmSource
   4229  } = DiscoveryStream.experimentData;
   4230  let queryParams = `?utm_source=${utmSource}`;
   4231  if (utmCampaign && utmContent) {
   4232    queryParams += `&utm_content=${utmContent}&utm_campaign=${utmCampaign}`;
   4233  }
   4234  const topics = [{
   4235    label: "Technology",
   4236    name: "technology"
   4237  }, {
   4238    label: "Science",
   4239    name: "science"
   4240  }, {
   4241    label: "Self-Improvement",
   4242    name: "self-improvement"
   4243  }, {
   4244    label: "Travel",
   4245    name: "travel"
   4246  }, {
   4247    label: "Career",
   4248    name: "career"
   4249  }, {
   4250    label: "Entertainment",
   4251    name: "entertainment"
   4252  }, {
   4253    label: "Food",
   4254    name: "food"
   4255  }, {
   4256    label: "Health",
   4257    name: "health"
   4258  }, {
   4259    label: "Must-Reads",
   4260    name: "must-reads",
   4261    url: `https://getpocket.com/collections${queryParams}`
   4262  }];
   4263  function onLinkClick(topic, positionInCard) {
   4264    if (dispatch) {
   4265      dispatch(actionCreators.DiscoveryStreamUserEvent({
   4266        event: "CLICK",
   4267        source,
   4268        action_position: position,
   4269        value: {
   4270          card_type: "topics_widget",
   4271          topic,
   4272          ...(positionInCard || positionInCard === 0 ? {
   4273            position_in_card: positionInCard
   4274          } : {}),
   4275          section_position: position
   4276        }
   4277      }));
   4278      dispatch(actionCreators.ImpressionStats({
   4279        source,
   4280        click: 0,
   4281        window_inner_width: props.windowObj.innerWidth,
   4282        window_inner_height: props.windowObj.innerHeight,
   4283        tiles: [{
   4284          id,
   4285          pos: position
   4286        }]
   4287      }));
   4288    }
   4289  }
   4290  function mapTopicItem(topic, index) {
   4291    return /*#__PURE__*/external_React_default().createElement("li", {
   4292      key: topic.name,
   4293      className: topic.overflow ? "ds-topics-widget-list-overflow-item" : ""
   4294    }, /*#__PURE__*/external_React_default().createElement(SafeAnchor, {
   4295      url: topic.url || `https://getpocket.com/explore/${topic.name}${queryParams}`,
   4296      dispatch: dispatch,
   4297      onLinkClick: () => onLinkClick(topic.name, index)
   4298    }, topic.label));
   4299  }
   4300  return /*#__PURE__*/external_React_default().createElement("div", {
   4301    className: "ds-topics-widget"
   4302  }, /*#__PURE__*/external_React_default().createElement("header", {
   4303    className: "ds-topics-widget-header"
   4304  }, "Popular Topics"), /*#__PURE__*/external_React_default().createElement("hr", null), /*#__PURE__*/external_React_default().createElement("div", {
   4305    className: "ds-topics-widget-list-container"
   4306  }, /*#__PURE__*/external_React_default().createElement("ul", null, topics.map(mapTopicItem))), /*#__PURE__*/external_React_default().createElement(SafeAnchor, {
   4307    className: "ds-topics-widget-button button primary",
   4308    url: `https://getpocket.com/${queryParams}`,
   4309    dispatch: dispatch,
   4310    onLinkClick: () => onLinkClick("more-topics")
   4311  }, "More Topics"), /*#__PURE__*/external_React_default().createElement(ImpressionStats_ImpressionStats, {
   4312    dispatch: dispatch,
   4313    rows: [{
   4314      id,
   4315      pos: position
   4316    }],
   4317    source: source
   4318  }));
   4319 }
   4320 _TopicsWidget.defaultProps = {
   4321  windowObj: window // Added to support unit tests
   4322 };
   4323 const TopicsWidget = (0,external_ReactRedux_namespaceObject.connect)(state => ({
   4324  DiscoveryStream: state.DiscoveryStream
   4325 }))(_TopicsWidget);
   4326 ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/AdBannerContextMenu/AdBannerContextMenu.jsx
   4327 /* This Source Code Form is subject to the terms of the Mozilla Public
   4328 * License, v. 2.0. If a copy of the MPL was not distributed with this
   4329 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
   4330 
   4331 
   4332 
   4333 
   4334 
   4335 /**
   4336 * A context menu for IAB banners (e.g. billboard, leaderboard).
   4337 *
   4338 * Note: MREC ad formats and sponsored stories share the context menu with
   4339 * other cards: make sure you also look at DSLinkMenu component
   4340 * to keep any updates to ad-related context menu items in sync.
   4341 *
   4342 * @param dispatch
   4343 * @param spoc
   4344 * @param position
   4345 * @param type
   4346 * @param showAdReporting
   4347 * @returns {Element}
   4348 * @class
   4349 */
   4350 function AdBannerContextMenu({
   4351  dispatch,
   4352  spoc,
   4353  position,
   4354  type,
   4355  showAdReporting,
   4356  toggleActive = () => {}
   4357 }) {
   4358  const ADBANNER_CONTEXT_MENU_OPTIONS = ["BlockAdUrl", ...(showAdReporting ? ["ReportAd"] : []), "ManageSponsoredContent", "OurSponsorsAndYourPrivacy"];
   4359  const [showContextMenu, setShowContextMenu] = (0,external_React_namespaceObject.useState)(false);
   4360  const [contextMenuClassNames, setContextMenuClassNames] = (0,external_React_namespaceObject.useState)("ads-context-menu");
   4361 
   4362  // The keyboard access parameter is passed down to LinkMenu component
   4363  // that uses it to focus on the first context menu option for accessibility.
   4364  const [isKeyboardAccess, setIsKeyboardAccess] = (0,external_React_namespaceObject.useState)(false);
   4365 
   4366  /**
   4367   * Toggles the style fix for context menu hover/active styles.
   4368   * This allows us to have unobtrusive, transparent button background by default,
   4369   * yet flip it over to semi-transparent grey when the menu is visible.
   4370   *
   4371   * @param contextMenuOpen
   4372   */
   4373  const toggleContextMenuStyleSwitch = contextMenuOpen => {
   4374    if (contextMenuOpen) {
   4375      setContextMenuClassNames("ads-context-menu context-menu-open");
   4376    } else {
   4377      setContextMenuClassNames("ads-context-menu");
   4378    }
   4379  };
   4380 
   4381  /**
   4382   * Toggles the context menu to open or close. Sets state depending on whether
   4383   * the context menu is accessed by mouse or keyboard.
   4384   *
   4385   * @param isKeyBoard
   4386   */
   4387  const toggleContextMenu = isKeyBoard => {
   4388    toggleContextMenuStyleSwitch(!showContextMenu);
   4389    toggleActive(!showContextMenu);
   4390    setShowContextMenu(!showContextMenu);
   4391    setIsKeyboardAccess(isKeyBoard);
   4392  };
   4393  const onClick = e => {
   4394    e.preventDefault();
   4395    toggleContextMenu(false);
   4396  };
   4397  const onKeyDown = e => {
   4398    if (e.key === "Enter" || e.key === " ") {
   4399      e.preventDefault();
   4400      toggleContextMenu(true);
   4401    }
   4402  };
   4403  const onUpdate = () => {
   4404    toggleContextMenuStyleSwitch(!showContextMenu);
   4405    toggleActive(!showContextMenu);
   4406    setShowContextMenu(!showContextMenu);
   4407  };
   4408  return /*#__PURE__*/external_React_default().createElement("div", {
   4409    className: "ads-context-menu-wrapper"
   4410  }, /*#__PURE__*/external_React_default().createElement("div", {
   4411    className: contextMenuClassNames
   4412  }, /*#__PURE__*/external_React_default().createElement("moz-button", {
   4413    type: "icon",
   4414    size: "default",
   4415    "data-l10n-id": "newtab-menu-content-tooltip",
   4416    "data-l10n-args": JSON.stringify({
   4417      title: spoc.title || spoc.sponsor || spoc.alt_text
   4418    }),
   4419    iconsrc: "chrome://global/skin/icons/more.svg",
   4420    onClick: onClick,
   4421    onKeyDown: onKeyDown
   4422  }), showContextMenu && /*#__PURE__*/external_React_default().createElement(LinkMenu, {
   4423    onUpdate: onUpdate,
   4424    dispatch: dispatch,
   4425    keyboardAccess: isKeyboardAccess,
   4426    options: ADBANNER_CONTEXT_MENU_OPTIONS,
   4427    shouldSendImpressionStats: true,
   4428    userEvent: actionCreators.DiscoveryStreamUserEvent,
   4429    site: {
   4430      // Props we want to pass on for new ad types that come from Unified Ads API
   4431      block_key: spoc.block_key,
   4432      fetchTimestamp: spoc.fetchTimestamp,
   4433      flight_id: spoc.flight_id,
   4434      format: spoc.format,
   4435      id: spoc.id,
   4436      guid: spoc.guid,
   4437      card_type: "spoc",
   4438      // required to record telemetry for an action, see handleBlockUrl in TelemetryFeed.sys.mjs
   4439      is_pocket_card: true,
   4440      position,
   4441      sponsor: spoc.sponsor,
   4442      title: spoc.title,
   4443      url: spoc.url || spoc.shim.url,
   4444      personalization_models: spoc.personalization_models,
   4445      priority: spoc.priority,
   4446      score: spoc.score,
   4447      alt_text: spoc.alt_text,
   4448      shim: spoc.shim
   4449    },
   4450    index: position,
   4451    source: type.toUpperCase()
   4452  })));
   4453 }
   4454 ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/PromoCard/PromoCard.jsx
   4455 /* This Source Code Form is subject to the terms of the Mozilla Public
   4456 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   4457 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   4458 
   4459 
   4460 
   4461 
   4462 
   4463 const PREF_PROMO_CARD_DISMISSED = "discoverystream.promoCard.visible";
   4464 
   4465 /**
   4466 * The PromoCard component displays a promotional message.
   4467 * It is used next to the AdBanner component in a four-column layout.
   4468 */
   4469 
   4470 const PromoCard = () => {
   4471  const dispatch = (0,external_ReactRedux_namespaceObject.useDispatch)();
   4472  const onCtaClick = (0,external_React_namespaceObject.useCallback)(() => {
   4473    dispatch(actionCreators.AlsoToMain({
   4474      type: actionTypes.PROMO_CARD_CLICK
   4475    }));
   4476  }, [dispatch]);
   4477  const onDismissClick = (0,external_React_namespaceObject.useCallback)(() => {
   4478    dispatch(actionCreators.AlsoToMain({
   4479      type: actionTypes.PROMO_CARD_DISMISS
   4480    }));
   4481    dispatch(actionCreators.SetPref(PREF_PROMO_CARD_DISMISSED, false));
   4482  }, [dispatch]);
   4483  const handleIntersection = (0,external_React_namespaceObject.useCallback)(() => {
   4484    dispatch(actionCreators.AlsoToMain({
   4485      type: actionTypes.PROMO_CARD_IMPRESSION
   4486    }));
   4487  }, [dispatch]);
   4488  const ref = useIntersectionObserver(handleIntersection);
   4489  return /*#__PURE__*/external_React_default().createElement("div", {
   4490    className: "promo-card-wrapper",
   4491    ref: el => {
   4492      ref.current = [el];
   4493    }
   4494  }, /*#__PURE__*/external_React_default().createElement("div", {
   4495    className: "promo-card-dismiss-button"
   4496  }, /*#__PURE__*/external_React_default().createElement("moz-button", {
   4497    type: "icon ghost",
   4498    size: "small",
   4499    "data-l10n-id": "newtab-promo-card-dismiss-button",
   4500    iconsrc: "chrome://global/skin/icons/close.svg",
   4501    onClick: onDismissClick,
   4502    onKeyDown: onDismissClick
   4503  })), /*#__PURE__*/external_React_default().createElement("div", {
   4504    className: "promo-card-inner"
   4505  }, /*#__PURE__*/external_React_default().createElement("div", {
   4506    className: "img-wrapper"
   4507  }, /*#__PURE__*/external_React_default().createElement("img", {
   4508    src: "chrome://newtab/content/data/content/assets/puzzle-fox.svg",
   4509    alt: ""
   4510  })), /*#__PURE__*/external_React_default().createElement("span", {
   4511    className: "promo-card-title",
   4512    "data-l10n-id": "newtab-promo-card-title"
   4513  }), /*#__PURE__*/external_React_default().createElement("span", {
   4514    className: "promo-card-body",
   4515    "data-l10n-id": "newtab-promo-card-body"
   4516  }), /*#__PURE__*/external_React_default().createElement("span", {
   4517    className: "promo-card-cta-wrapper"
   4518  }, /*#__PURE__*/external_React_default().createElement("a", {
   4519    href: "https://support.mozilla.org/kb/sponsor-privacy",
   4520    "data-l10n-id": "newtab-promo-card-cta",
   4521    target: "_blank",
   4522    rel: "noreferrer",
   4523    onClick: onCtaClick
   4524  }))));
   4525 };
   4526 
   4527 ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/AdBanner/AdBanner.jsx
   4528 /* This Source Code Form is subject to the terms of the Mozilla Public
   4529 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   4530 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   4531 
   4532 
   4533 
   4534 
   4535 
   4536 
   4537 
   4538 const AdBanner_PREF_SECTIONS_ENABLED = "discoverystream.sections.enabled";
   4539 const AdBanner_PREF_OHTTP_UNIFIED_ADS = "unifiedAds.ohttp.enabled";
   4540 const PREF_REPORT_ADS_ENABLED = "discoverystream.reportAds.enabled";
   4541 const PREF_PROMOCARD_ENABLED = "discoverystream.promoCard.enabled";
   4542 const PREF_PROMOCARD_VISIBLE = "discoverystream.promoCard.visible";
   4543 
   4544 /**
   4545 * A new banner ad that appears between rows of stories: leaderboard or billboard size.
   4546 *
   4547 * @param spoc
   4548 * @param dispatch
   4549 * @param firstVisibleTimestamp
   4550 * @param row
   4551 * @param type
   4552 * @param prefs
   4553 * @returns {Element}
   4554 * @class
   4555 */
   4556 const AdBanner = ({
   4557  spoc,
   4558  dispatch,
   4559  firstVisibleTimestamp,
   4560  row,
   4561  type,
   4562  prefs
   4563 }) => {
   4564  const getDimensions = format => {
   4565    switch (format) {
   4566      case "leaderboard":
   4567        return {
   4568          width: "728",
   4569          height: "90"
   4570        };
   4571      case "billboard":
   4572        return {
   4573          width: "970",
   4574          height: "250"
   4575        };
   4576    }
   4577    return {
   4578      // image will still render with default values
   4579      width: undefined,
   4580      height: undefined
   4581    };
   4582  };
   4583  const promoCardEnabled = spoc.format === "billboard" && prefs[PREF_PROMOCARD_ENABLED] && prefs[PREF_PROMOCARD_VISIBLE];
   4584  const sectionsEnabled = prefs[AdBanner_PREF_SECTIONS_ENABLED];
   4585  const ohttpEnabled = prefs[AdBanner_PREF_OHTTP_UNIFIED_ADS];
   4586  const showAdReporting = prefs[PREF_REPORT_ADS_ENABLED];
   4587  const ohttpImagesEnabled = prefs.ohttpImagesConfig?.enabled;
   4588  const [menuActive, setMenuActive] = (0,external_React_namespaceObject.useState)(false);
   4589  const adBannerWrapperClassName = `ad-banner-wrapper ${menuActive ? "active" : ""} ${promoCardEnabled ? "promo-card" : ""}`;
   4590  const {
   4591    width: imgWidth,
   4592    height: imgHeight
   4593  } = getDimensions(spoc.format);
   4594  const onLinkClick = () => {
   4595    dispatch(actionCreators.DiscoveryStreamUserEvent({
   4596      event: "CLICK",
   4597      source: type.toUpperCase(),
   4598      // Banner ads don't have a position, but a row number
   4599      action_position: parseInt(row, 10),
   4600      value: {
   4601        card_type: "spoc",
   4602        tile_id: spoc.id,
   4603        ...(spoc.shim?.click ? {
   4604          shim: spoc.shim.click
   4605        } : {}),
   4606        fetchTimestamp: spoc.fetchTimestamp,
   4607        firstVisibleTimestamp,
   4608        format: spoc.format,
   4609        ...(sectionsEnabled ? {
   4610          section: spoc.format,
   4611          section_position: parseInt(row, 10)
   4612        } : {})
   4613      }
   4614    }));
   4615  };
   4616  const toggleActive = active => {
   4617    setMenuActive(active);
   4618  };
   4619 
   4620  // in the default card grid 1 would come before the 1st row of cards and 9 comes after the last row
   4621  // using clamp to make sure its between valid values (1-9)
   4622  const clampedRow = Math.max(1, Math.min(9, row));
   4623  const secureImage = ohttpImagesEnabled && ohttpEnabled;
   4624  let rawImageSrc = spoc.raw_image_src;
   4625 
   4626  // Wraps the image URL with the moz-cached-ohttp:// protocol.
   4627  // This enables Firefox to load resources over Oblivious HTTP (OHTTP),
   4628  // providing privacy-preserving resource loading.
   4629  // Applied only when inferred personalization is enabled.
   4630  // See: https://firefox-source-docs.mozilla.org/browser/components/mozcachedohttp/docs/index.html
   4631  if (secureImage) {
   4632    rawImageSrc = `moz-cached-ohttp://newtab-image/?url=${encodeURIComponent(spoc.raw_image_src)}`;
   4633  }
   4634  return /*#__PURE__*/external_React_default().createElement("aside", {
   4635    className: adBannerWrapperClassName,
   4636    style: {
   4637      gridRow: clampedRow
   4638    }
   4639  }, /*#__PURE__*/external_React_default().createElement("div", {
   4640    className: `ad-banner-inner ${spoc.format}`
   4641  }, /*#__PURE__*/external_React_default().createElement(SafeAnchor, {
   4642    className: "ad-banner-link",
   4643    url: spoc.url,
   4644    title: spoc.title || spoc.sponsor || spoc.alt_text,
   4645    onLinkClick: onLinkClick,
   4646    dispatch: dispatch,
   4647    isSponsored: true
   4648  }, /*#__PURE__*/external_React_default().createElement(ImpressionStats_ImpressionStats, {
   4649    flightId: spoc.flight_id,
   4650    rows: [{
   4651      id: spoc.id,
   4652      card_type: "spoc",
   4653      pos: row,
   4654      recommended_at: spoc.recommended_at,
   4655      received_rank: spoc.received_rank,
   4656      format: spoc.format,
   4657      ...(spoc.shim?.impression ? {
   4658        shim: spoc.shim.impression
   4659      } : {})
   4660    }],
   4661    dispatch: dispatch,
   4662    firstVisibleTimestamp: firstVisibleTimestamp
   4663  }), /*#__PURE__*/external_React_default().createElement("div", {
   4664    className: "ad-banner-content"
   4665  }, /*#__PURE__*/external_React_default().createElement("img", {
   4666    src: rawImageSrc,
   4667    alt: spoc.alt_text,
   4668    loading: "eager",
   4669    width: imgWidth,
   4670    height: imgHeight
   4671  })), /*#__PURE__*/external_React_default().createElement("div", {
   4672    className: "ad-banner-sponsored"
   4673  }, /*#__PURE__*/external_React_default().createElement("span", {
   4674    className: "ad-banner-sponsored-label",
   4675    "data-l10n-id": "newtab-label-sponsored-fixed"
   4676  }))), /*#__PURE__*/external_React_default().createElement("div", {
   4677    className: "ad-banner-hover-background"
   4678  }, /*#__PURE__*/external_React_default().createElement(AdBannerContextMenu, {
   4679    dispatch: dispatch,
   4680    spoc: spoc,
   4681    position: row,
   4682    type: type,
   4683    showAdReporting: showAdReporting,
   4684    toggleActive: toggleActive
   4685  }))), promoCardEnabled && /*#__PURE__*/external_React_default().createElement(PromoCard, null));
   4686 };
   4687 ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/CardGrid/CardGrid.jsx
   4688 /* This Source Code Form is subject to the terms of the Mozilla Public
   4689 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   4690 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   4691 
   4692 
   4693 
   4694 
   4695 
   4696 
   4697 
   4698 
   4699 const PREF_SECTIONS_CARDS_ENABLED = "discoverystream.sections.cards.enabled";
   4700 const PREF_TOPICS_ENABLED = "discoverystream.topicLabels.enabled";
   4701 const PREF_TOPICS_SELECTED = "discoverystream.topicSelection.selectedTopics";
   4702 const PREF_TOPICS_AVAILABLE = "discoverystream.topicSelection.topics";
   4703 const PREF_SPOCS_STARTUPCACHE_ENABLED = "discoverystream.spocs.startupCache.enabled";
   4704 const PREF_BILLBOARD_ENABLED = "newtabAdSize.billboard";
   4705 const PREF_BILLBOARD_POSITION = "newtabAdSize.billboard.position";
   4706 const PREF_LEADERBOARD_ENABLED = "newtabAdSize.leaderboard";
   4707 const PREF_LEADERBOARD_POSITION = "newtabAdSize.leaderboard.position";
   4708 const WIDGET_IDS = {
   4709  TOPICS: 1
   4710 };
   4711 function DSSubHeader({
   4712  children
   4713 }) {
   4714  return /*#__PURE__*/external_React_default().createElement("div", {
   4715    className: "section-top-bar ds-sub-header"
   4716  }, /*#__PURE__*/external_React_default().createElement("h3", {
   4717    className: "section-title-container"
   4718  }, children));
   4719 }
   4720 
   4721 // eslint-disable-next-line no-shadow
   4722 function CardGrid_IntersectionObserver({
   4723  children,
   4724  windowObj = window,
   4725  onIntersecting
   4726 }) {
   4727  const intersectionElement = (0,external_React_namespaceObject.useRef)(null);
   4728  (0,external_React_namespaceObject.useEffect)(() => {
   4729    let observer;
   4730    if (!observer && onIntersecting && intersectionElement.current) {
   4731      observer = new windowObj.IntersectionObserver(entries => {
   4732        const entry = entries.find(e => e.isIntersecting);
   4733        if (entry) {
   4734          // Stop observing since element has been seen
   4735          if (observer && intersectionElement.current) {
   4736            observer.unobserve(intersectionElement.current);
   4737          }
   4738          onIntersecting();
   4739        }
   4740      });
   4741      observer.observe(intersectionElement.current);
   4742    }
   4743    // Cleanup
   4744    return () => observer?.disconnect();
   4745  }, [windowObj, onIntersecting]);
   4746  return /*#__PURE__*/external_React_default().createElement("div", {
   4747    ref: intersectionElement
   4748  }, children);
   4749 }
   4750 class _CardGrid extends (external_React_default()).PureComponent {
   4751  constructor(props) {
   4752    super(props);
   4753    this.state = {
   4754      focusedIndex: 0
   4755    };
   4756    this.onCardFocus = this.onCardFocus.bind(this);
   4757    this.handleCardKeyDown = this.handleCardKeyDown.bind(this);
   4758  }
   4759  onCardFocus(index) {
   4760    this.setState({
   4761      focusedIndex: index
   4762    });
   4763  }
   4764  handleCardKeyDown(e) {
   4765    if (e.key === "ArrowLeft" || e.key === "ArrowRight") {
   4766      e.preventDefault();
   4767      const currentCardEl = e.target.closest("article.ds-card");
   4768      if (!currentCardEl) {
   4769        return;
   4770      }
   4771 
   4772      // Arrow direction should match visual navigation direction in RTL
   4773      const isRTL = document.dir === "rtl";
   4774      const navigateToPrevious = isRTL ? e.key === "ArrowRight" : e.key === "ArrowLeft";
   4775      let targetCardEl = currentCardEl;
   4776 
   4777      // Walk through siblings to find the target card element
   4778      while (targetCardEl) {
   4779        targetCardEl = navigateToPrevious ? targetCardEl.previousElementSibling : targetCardEl.nextElementSibling;
   4780        if (targetCardEl && targetCardEl.matches("article.ds-card")) {
   4781          const link = targetCardEl.querySelector("a.ds-card-link");
   4782          if (link) {
   4783            link.focus();
   4784          }
   4785          break;
   4786        }
   4787      }
   4788    }
   4789  }
   4790 
   4791  // eslint-disable-next-line max-statements
   4792  renderCards() {
   4793    const prefs = this.props.Prefs.values;
   4794    const {
   4795      items,
   4796      ctaButtonSponsors,
   4797      ctaButtonVariant,
   4798      widgets,
   4799      DiscoveryStream
   4800    } = this.props;
   4801    const {
   4802      topicsLoading
   4803    } = DiscoveryStream;
   4804    const mayHaveSectionsCards = prefs[PREF_SECTIONS_CARDS_ENABLED];
   4805    const showTopics = prefs[PREF_TOPICS_ENABLED];
   4806    const selectedTopics = prefs[PREF_TOPICS_SELECTED];
   4807    const availableTopics = prefs[PREF_TOPICS_AVAILABLE];
   4808    const spocsStartupCacheEnabled = prefs[PREF_SPOCS_STARTUPCACHE_ENABLED];
   4809    const billboardEnabled = prefs[PREF_BILLBOARD_ENABLED];
   4810    const leaderboardEnabled = prefs[PREF_LEADERBOARD_ENABLED];
   4811    const recs = this.props.data.recommendations.slice(0, items);
   4812    const cards = [];
   4813    let cardIndex = 0;
   4814    for (let index = 0; index < items; index++) {
   4815      const rec = recs[index];
   4816      const isPlaceholder = topicsLoading || this.props.placeholder || !rec || rec.placeholder || rec.flight_id && !spocsStartupCacheEnabled && this.props.App.isForStartupCache.DiscoveryStream;
   4817      if (isPlaceholder) {
   4818        cards.push(/*#__PURE__*/external_React_default().createElement(PlaceholderDSCard, {
   4819          key: `dscard-${index}`
   4820        }));
   4821      } else {
   4822        const currentCardIndex = cardIndex;
   4823        cardIndex++;
   4824        cards.push(/*#__PURE__*/external_React_default().createElement(DSCard, {
   4825          key: `dscard-${rec.id}`,
   4826          pos: rec.pos,
   4827          flightId: rec.flight_id,
   4828          image_src: rec.image_src,
   4829          raw_image_src: rec.raw_image_src,
   4830          icon_src: rec.icon_src,
   4831          word_count: rec.word_count,
   4832          time_to_read: rec.time_to_read,
   4833          title: rec.title,
   4834          topic: rec.topic,
   4835          features: rec.features,
   4836          showTopics: showTopics,
   4837          selectedTopics: selectedTopics,
   4838          excerpt: rec.excerpt,
   4839          availableTopics: availableTopics,
   4840          url: rec.url,
   4841          id: rec.id,
   4842          shim: rec.shim,
   4843          fetchTimestamp: rec.fetchTimestamp,
   4844          type: this.props.type,
   4845          context: rec.context,
   4846          sponsor: rec.sponsor,
   4847          sponsored_by_override: rec.sponsored_by_override,
   4848          dispatch: this.props.dispatch,
   4849          source: rec.domain,
   4850          publisher: rec.publisher,
   4851          pocket_id: rec.pocket_id,
   4852          context_type: rec.context_type,
   4853          bookmarkGuid: rec.bookmarkGuid,
   4854          ctaButtonSponsors: ctaButtonSponsors,
   4855          ctaButtonVariant: ctaButtonVariant,
   4856          recommendation_id: rec.recommendation_id,
   4857          firstVisibleTimestamp: this.props.firstVisibleTimestamp,
   4858          mayHaveSectionsCards: mayHaveSectionsCards,
   4859          corpus_item_id: rec.corpus_item_id,
   4860          scheduled_corpus_item_id: rec.scheduled_corpus_item_id,
   4861          recommended_at: rec.recommended_at,
   4862          received_rank: rec.received_rank,
   4863          format: rec.format,
   4864          alt_text: rec.alt_text,
   4865          isTimeSensitive: rec.isTimeSensitive,
   4866          tabIndex: currentCardIndex === this.state.focusedIndex ? 0 : -1,
   4867          onFocus: () => this.onCardFocus(currentCardIndex),
   4868          attribution: rec.attribution
   4869        }));
   4870      }
   4871    }
   4872    if (widgets?.positions?.length && widgets?.data?.length) {
   4873      let positionIndex = 0;
   4874      const source = "CARDGRID_WIDGET";
   4875      for (const widget of widgets.data) {
   4876        let widgetComponent = null;
   4877        const position = widgets.positions[positionIndex];
   4878 
   4879        // Stop if we run out of positions to place widgets.
   4880        if (!position) {
   4881          break;
   4882        }
   4883        switch (widget?.type) {
   4884          case "TopicsWidget":
   4885            widgetComponent = /*#__PURE__*/external_React_default().createElement(TopicsWidget, {
   4886              position: position.index,
   4887              dispatch: this.props.dispatch,
   4888              source: source,
   4889              id: WIDGET_IDS.TOPICS
   4890            });
   4891            break;
   4892        }
   4893        if (widgetComponent) {
   4894          // We found a widget, so up the position for next try.
   4895          positionIndex++;
   4896          // We replace an existing card with the widget.
   4897          cards.splice(position.index, 1, widgetComponent);
   4898        }
   4899      }
   4900    }
   4901 
   4902    // if a banner ad is enabled and we have any available, place them in the grid
   4903    const {
   4904      spocs
   4905    } = this.props.DiscoveryStream;
   4906    if ((billboardEnabled || leaderboardEnabled) && spocs?.data?.newtab_spocs?.items) {
   4907      // Only render one AdBanner in the grid -
   4908      // Prioritize rendering a leaderboard if it exists,
   4909      // otherwise render a billboard
   4910      const spocToRender = spocs.data.newtab_spocs.items.find(({
   4911        format
   4912      }) => format === "leaderboard" && leaderboardEnabled) || spocs.data.newtab_spocs.items.find(({
   4913        format
   4914      }) => format === "billboard" && billboardEnabled);
   4915      if (spocToRender && !spocs.blocked.includes(spocToRender.url)) {
   4916        const row = spocToRender.format === "leaderboard" ? prefs[PREF_LEADERBOARD_POSITION] : prefs[PREF_BILLBOARD_POSITION];
   4917        function displayCardsPerRow() {
   4918          // Determines the number of cards per row based on the window width:
   4919          // width <= 1122px: 2 cards per row
   4920          // width 1123px to 1697px: 3 cards per row
   4921          // width >= 1698px: 4 cards per row
   4922          if (window.innerWidth <= 1122) {
   4923            return 2;
   4924          } else if (window.innerWidth > 1122 && window.innerWidth < 1698) {
   4925            return 3;
   4926          }
   4927          return 4;
   4928        }
   4929        const injectAdBanner = bannerIndex => {
   4930          // .splice() inserts the AdBanner at the desired index, ensuring correct DOM order for accessibility and keyboard navigation.
   4931          // .push() would place it at the end, which is visually incorrect even if adjusted with CSS.
   4932          cards.splice(bannerIndex, 0, /*#__PURE__*/external_React_default().createElement(AdBanner, {
   4933            spoc: spocToRender,
   4934            key: `dscard-${spocToRender.id}`,
   4935            dispatch: this.props.dispatch,
   4936            type: this.props.type,
   4937            firstVisibleTimestamp: this.props.firstVisibleTimestamp,
   4938            row: row,
   4939            prefs: prefs
   4940          }));
   4941        };
   4942        const getBannerIndex = () => {
   4943          // Calculate the index for where the AdBanner should be added, depending on number of cards per row on the grid
   4944          const cardsPerRow = displayCardsPerRow();
   4945          let bannerIndex = (row - 1) * cardsPerRow;
   4946          return bannerIndex;
   4947        };
   4948        injectAdBanner(getBannerIndex());
   4949      }
   4950    }
   4951    const gridClassName = this.renderGridClassName();
   4952    return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, cards?.length > 0 && /*#__PURE__*/external_React_default().createElement("div", {
   4953      className: gridClassName,
   4954      onKeyDown: this.handleCardKeyDown
   4955    }, cards));
   4956  }
   4957  renderGridClassName() {
   4958    const {
   4959      hybridLayout,
   4960      hideCardBackground,
   4961      fourCardLayout,
   4962      compactGrid,
   4963      hideDescriptions
   4964    } = this.props;
   4965    const hideCardBackgroundClass = hideCardBackground ? `ds-card-grid-hide-background` : ``;
   4966    const fourCardLayoutClass = fourCardLayout ? `ds-card-grid-four-card-variant` : ``;
   4967    const hideDescriptionsClassName = !hideDescriptions ? `ds-card-grid-include-descriptions` : ``;
   4968    const compactGridClassName = compactGrid ? `ds-card-grid-compact` : ``;
   4969    const hybridLayoutClassName = hybridLayout ? `ds-card-grid-hybrid-layout` : ``;
   4970    const gridClassName = `ds-card-grid ${hybridLayoutClassName} ${hideCardBackgroundClass} ${fourCardLayoutClass} ${hideDescriptionsClassName} ${compactGridClassName}`;
   4971    return gridClassName;
   4972  }
   4973  render() {
   4974    const {
   4975      data
   4976    } = this.props;
   4977 
   4978    // Handle a render before feed has been fetched by displaying nothing
   4979    if (!data) {
   4980      return null;
   4981    }
   4982 
   4983    // Handle the case where a user has dismissed all recommendations
   4984    const isEmpty = data.recommendations.length === 0;
   4985    return /*#__PURE__*/external_React_default().createElement("div", null, this.props.title && /*#__PURE__*/external_React_default().createElement("div", {
   4986      className: "ds-header"
   4987    }, /*#__PURE__*/external_React_default().createElement("div", {
   4988      className: "title"
   4989    }, this.props.title), this.props.context && /*#__PURE__*/external_React_default().createElement(FluentOrText, {
   4990      message: this.props.context
   4991    }, /*#__PURE__*/external_React_default().createElement("div", {
   4992      className: "ds-context"
   4993    }))), isEmpty ? /*#__PURE__*/external_React_default().createElement("div", {
   4994      className: "ds-card-grid empty"
   4995    }, /*#__PURE__*/external_React_default().createElement(DSEmptyState, {
   4996      status: data.status,
   4997      dispatch: this.props.dispatch,
   4998      feed: this.props.feed
   4999    })) : this.renderCards());
   5000  }
   5001 }
   5002 _CardGrid.defaultProps = {
   5003  items: 4 // Number of stories to display
   5004 };
   5005 const CardGrid = (0,external_ReactRedux_namespaceObject.connect)(state => ({
   5006  Prefs: state.Prefs,
   5007  App: state.App,
   5008  DiscoveryStream: state.DiscoveryStream
   5009 }))(_CardGrid);
   5010 ;// CONCATENATED MODULE: ./content-src/components/A11yLinkButton/A11yLinkButton.jsx
   5011 function A11yLinkButton_extends() { return A11yLinkButton_extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, A11yLinkButton_extends.apply(null, arguments); }
   5012 /* This Source Code Form is subject to the terms of the Mozilla Public
   5013 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   5014 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   5015 
   5016 
   5017 function A11yLinkButton(props) {
   5018  // function for merging classes, if necessary
   5019  let className = "a11y-link-button";
   5020  if (props.className) {
   5021    className += ` ${props.className}`;
   5022  }
   5023  return /*#__PURE__*/external_React_default().createElement("button", A11yLinkButton_extends({
   5024    type: "button"
   5025  }, props, {
   5026    className: className
   5027  }), props.children);
   5028 }
   5029 ;// CONCATENATED MODULE: ./content-src/components/ErrorBoundary/ErrorBoundary.jsx
   5030 /* This Source Code Form is subject to the terms of the Mozilla Public
   5031 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   5032 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   5033 
   5034 
   5035 
   5036 class ErrorBoundaryFallback extends (external_React_default()).PureComponent {
   5037  constructor(props) {
   5038    super(props);
   5039    this.windowObj = this.props.windowObj || window;
   5040    this.onClick = this.onClick.bind(this);
   5041  }
   5042 
   5043  /**
   5044   * Since we only get here if part of the page has crashed, do a
   5045   * forced reload to give us the best chance at recovering.
   5046   */
   5047  onClick() {
   5048    this.windowObj.location.reload(true);
   5049  }
   5050  render() {
   5051    const defaultClass = "as-error-fallback";
   5052    let className;
   5053    if ("className" in this.props) {
   5054      className = `${this.props.className} ${defaultClass}`;
   5055    } else {
   5056      className = defaultClass;
   5057    }
   5058 
   5059    // "A11yLinkButton" to force normal link styling stuff (eg cursor on hover)
   5060    return /*#__PURE__*/external_React_default().createElement("div", {
   5061      className: className
   5062    }, /*#__PURE__*/external_React_default().createElement("div", {
   5063      "data-l10n-id": "newtab-error-fallback-info"
   5064    }), /*#__PURE__*/external_React_default().createElement("span", null, /*#__PURE__*/external_React_default().createElement(A11yLinkButton, {
   5065      className: "reload-button",
   5066      onClick: this.onClick,
   5067      "data-l10n-id": "newtab-error-fallback-refresh-link"
   5068    })));
   5069  }
   5070 }
   5071 ErrorBoundaryFallback.defaultProps = {
   5072  className: "as-error-fallback"
   5073 };
   5074 class ErrorBoundary extends (external_React_default()).PureComponent {
   5075  constructor(props) {
   5076    super(props);
   5077    this.state = {
   5078      hasError: false
   5079    };
   5080  }
   5081  componentDidCatch() {
   5082    this.setState({
   5083      hasError: true
   5084    });
   5085  }
   5086  render() {
   5087    if (!this.state.hasError) {
   5088      return this.props.children;
   5089    }
   5090    return /*#__PURE__*/external_React_default().createElement(this.props.FallbackComponent, {
   5091      className: this.props.className
   5092    });
   5093  }
   5094 }
   5095 ErrorBoundary.defaultProps = {
   5096  FallbackComponent: ErrorBoundaryFallback
   5097 };
   5098 ;// CONCATENATED MODULE: ./content-src/components/CollapsibleSection/CollapsibleSection.jsx
   5099 /* This Source Code Form is subject to the terms of the Mozilla Public
   5100 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   5101 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   5102 
   5103 
   5104 
   5105 
   5106 
   5107 
   5108 
   5109 /**
   5110 * A section that can collapse. As of bug 1710937, it can no longer collapse.
   5111 * See bug 1727365 for follow-up work to simplify this component.
   5112 */
   5113 class _CollapsibleSection extends (external_React_default()).PureComponent {
   5114  constructor(props) {
   5115    super(props);
   5116    this.onBodyMount = this.onBodyMount.bind(this);
   5117    this.onMenuButtonMouseEnter = this.onMenuButtonMouseEnter.bind(this);
   5118    this.onMenuButtonMouseLeave = this.onMenuButtonMouseLeave.bind(this);
   5119    this.onMenuUpdate = this.onMenuUpdate.bind(this);
   5120    this.setContextMenuButtonRef = this.setContextMenuButtonRef.bind(this);
   5121    this.handleTopicSelectionButtonClick = this.handleTopicSelectionButtonClick.bind(this);
   5122    this.state = {
   5123      menuButtonHover: false,
   5124      showContextMenu: false
   5125    };
   5126  }
   5127  setContextMenuButtonRef(element) {
   5128    this.contextMenuButtonRef = element;
   5129  }
   5130  onBodyMount(node) {
   5131    this.sectionBody = node;
   5132  }
   5133  onMenuButtonMouseEnter() {
   5134    this.setState({
   5135      menuButtonHover: true
   5136    });
   5137  }
   5138  onMenuButtonMouseLeave() {
   5139    this.setState({
   5140      menuButtonHover: false
   5141    });
   5142  }
   5143  onMenuUpdate(showContextMenu) {
   5144    this.setState({
   5145      showContextMenu
   5146    });
   5147  }
   5148  handleTopicSelectionButtonClick() {
   5149    const maybeDisplay = this.props.Prefs.values["discoverystream.topicSelection.onboarding.maybeDisplay"];
   5150    this.props.dispatch(actionCreators.OnlyToMain({
   5151      type: actionTypes.TOPIC_SELECTION_USER_OPEN
   5152    }));
   5153    if (maybeDisplay) {
   5154      // if still part of onboarding, remove user from onboarding flow
   5155      this.props.dispatch(actionCreators.SetPref("discoverystream.topicSelection.onboarding.maybeDisplay", false));
   5156    }
   5157    this.props.dispatch(actionCreators.BroadcastToContent({
   5158      type: actionTypes.TOPIC_SELECTION_SPOTLIGHT_OPEN
   5159    }));
   5160  }
   5161  render() {
   5162    const {
   5163      isAnimating,
   5164      maxHeight,
   5165      menuButtonHover,
   5166      showContextMenu
   5167    } = this.state;
   5168    const {
   5169      id,
   5170      collapsed,
   5171      title,
   5172      subTitle,
   5173      mayHaveTopicsSelection,
   5174      sectionsEnabled
   5175    } = this.props;
   5176    const active = menuButtonHover || showContextMenu;
   5177    let bodyStyle;
   5178    if (isAnimating && !collapsed) {
   5179      bodyStyle = {
   5180        maxHeight
   5181      };
   5182    } else if (!isAnimating && collapsed) {
   5183      bodyStyle = {
   5184        display: "none"
   5185      };
   5186    }
   5187    let titleStyle;
   5188    if (this.props.hideTitle) {
   5189      titleStyle = {
   5190        visibility: "hidden"
   5191      };
   5192    }
   5193    const hasSubtitleClassName = subTitle ? `has-subtitle` : ``;
   5194    const hasBeenUpdatedPreviously = this.props.Prefs.values["discoverystream.topicSelection.hasBeenUpdatedPreviously"];
   5195    const selectedTopics = this.props.Prefs.values["discoverystream.topicSelection.selectedTopics"];
   5196    const topicsHaveBeenPreviouslySet = hasBeenUpdatedPreviously || selectedTopics;
   5197    return /*#__PURE__*/external_React_default().createElement("section", {
   5198      className: `collapsible-section ${this.props.className}${active ? " active" : ""}`
   5199      // Note: data-section-id is used for web extension api tests in mozilla central
   5200      ,
   5201      "data-section-id": id
   5202    }, !sectionsEnabled && /*#__PURE__*/external_React_default().createElement("div", {
   5203      className: "section-top-bar"
   5204    }, /*#__PURE__*/external_React_default().createElement("h2", {
   5205      className: `section-title-container ${hasSubtitleClassName}`,
   5206      style: titleStyle
   5207    }, /*#__PURE__*/external_React_default().createElement("span", {
   5208      className: "section-title"
   5209    }, /*#__PURE__*/external_React_default().createElement(FluentOrText, {
   5210      message: title
   5211    })), subTitle && /*#__PURE__*/external_React_default().createElement("span", {
   5212      className: "section-sub-title"
   5213    }, /*#__PURE__*/external_React_default().createElement(FluentOrText, {
   5214      message: subTitle
   5215    }))), mayHaveTopicsSelection && /*#__PURE__*/external_React_default().createElement("div", {
   5216      className: "button-topic-selection"
   5217    }, /*#__PURE__*/external_React_default().createElement("moz-button", {
   5218      "data-l10n-id": topicsHaveBeenPreviouslySet ? "newtab-topic-selection-button-update-interests" : "newtab-topic-selection-button-pick-interests",
   5219      type: topicsHaveBeenPreviouslySet ? "default" : "primary",
   5220      onClick: this.handleTopicSelectionButtonClick
   5221    }))), /*#__PURE__*/external_React_default().createElement(ErrorBoundary, {
   5222      className: "section-body-fallback"
   5223    }, /*#__PURE__*/external_React_default().createElement("div", {
   5224      ref: this.onBodyMount,
   5225      style: bodyStyle
   5226    }, this.props.children)));
   5227  }
   5228 }
   5229 _CollapsibleSection.defaultProps = {
   5230  document: globalThis.document || {
   5231    addEventListener: () => {},
   5232    removeEventListener: () => {},
   5233    visibilityState: "hidden"
   5234  }
   5235 };
   5236 const CollapsibleSection = (0,external_ReactRedux_namespaceObject.connect)(state => ({
   5237  Prefs: state.Prefs
   5238 }))(_CollapsibleSection);
   5239 ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/DSMessage/DSMessage.jsx
   5240 /* This Source Code Form is subject to the terms of the Mozilla Public
   5241 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   5242 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   5243 
   5244 
   5245 
   5246 
   5247 class DSMessage extends (external_React_default()).PureComponent {
   5248  render() {
   5249    return /*#__PURE__*/external_React_default().createElement("div", {
   5250      className: "ds-message"
   5251    }, /*#__PURE__*/external_React_default().createElement("header", {
   5252      className: "title"
   5253    }, this.props.icon && /*#__PURE__*/external_React_default().createElement("div", {
   5254      className: "glyph",
   5255      style: {
   5256        backgroundImage: `url(${this.props.icon})`
   5257      }
   5258    }), this.props.title && /*#__PURE__*/external_React_default().createElement("span", {
   5259      className: "title-text"
   5260    }, /*#__PURE__*/external_React_default().createElement(FluentOrText, {
   5261      message: this.props.title
   5262    })), this.props.link_text && this.props.link_url && /*#__PURE__*/external_React_default().createElement(SafeAnchor, {
   5263      className: "link",
   5264      url: this.props.link_url
   5265    }, /*#__PURE__*/external_React_default().createElement(FluentOrText, {
   5266      message: this.props.link_text
   5267    }))));
   5268  }
   5269 }
   5270 ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/ReportContent/ReportContent.jsx
   5271 /* This Source Code Form is subject to the terms of the Mozilla Public
   5272 * License, v. 2.0. If a copy of the MPL was not distributed with this
   5273 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
   5274 
   5275 
   5276 
   5277 const ReportContent = spocs => {
   5278  const dispatch = (0,external_ReactRedux_namespaceObject.useDispatch)();
   5279  const modal = (0,external_React_namespaceObject.useRef)(null);
   5280  const radioGroupRef = (0,external_React_namespaceObject.useRef)(null);
   5281  const submitButtonRef = (0,external_React_namespaceObject.useRef)(null);
   5282  const report = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.DiscoveryStream.report);
   5283  const [valueSelected, setValueSelected] = (0,external_React_namespaceObject.useState)(false);
   5284  const [selectedReason, setSelectedReason] = (0,external_React_namespaceObject.useState)(null);
   5285  const spocData = spocs.spocs.data;
   5286 
   5287  // Sends a dispatch to update the redux store when modal is cancelled
   5288  const handleCancel = () => {
   5289    dispatch(actionCreators.AlsoToMain({
   5290      type: actionTypes.REPORT_CLOSE
   5291    }));
   5292  };
   5293  const handleSubmit = (0,external_React_namespaceObject.useCallback)(() => {
   5294    const {
   5295      card_type,
   5296      corpus_item_id,
   5297      position,
   5298      reporting_url,
   5299      scheduled_corpus_item_id,
   5300      section_position,
   5301      section,
   5302      title,
   5303      topic,
   5304      url
   5305    } = report;
   5306    if (card_type === "organic") {
   5307      dispatch(actionCreators.AlsoToMain({
   5308        type: actionTypes.REPORT_CONTENT_SUBMIT,
   5309        data: {
   5310          card_type,
   5311          corpus_item_id,
   5312          report_reason: selectedReason,
   5313          scheduled_corpus_item_id,
   5314          section_position,
   5315          section,
   5316          title,
   5317          topic,
   5318          url
   5319        }
   5320      }));
   5321    } else if (card_type === "spoc") {
   5322      // Retrieve placement_id by comparing spocData with the ad that was reported
   5323      const getPlacementId = () => {
   5324        if (!spocData || !report.url) {
   5325          return null;
   5326        }
   5327        for (const [placementId, spocList] of Object.entries(spocData)) {
   5328          for (const spoc of Object.values(spocList)) {
   5329            if (spoc?.url === report.url) {
   5330              return placementId;
   5331            }
   5332          }
   5333        }
   5334        return null;
   5335      };
   5336      const placement_id = getPlacementId();
   5337      dispatch(actionCreators.AlsoToMain({
   5338        type: actionTypes.REPORT_AD_SUBMIT,
   5339        data: {
   5340          report_reason: selectedReason,
   5341          placement_id,
   5342          position,
   5343          reporting_url,
   5344          url
   5345        }
   5346      }));
   5347    }
   5348    dispatch(actionCreators.AlsoToMain({
   5349      type: actionTypes.BLOCK_URL,
   5350      data: [{
   5351        ...report
   5352      }]
   5353    }));
   5354    dispatch(actionCreators.OnlyToOneContent({
   5355      type: actionTypes.SHOW_TOAST_MESSAGE,
   5356      data: {
   5357        toastId: "reportSuccessToast",
   5358        showNotifications: true
   5359      }
   5360    }, "ActivityStream:Content"));
   5361  }, [dispatch, selectedReason, report, spocData]);
   5362 
   5363  // Opens and closes the modal based on user interaction
   5364  (0,external_React_namespaceObject.useEffect)(() => {
   5365    if (report.visible && modal?.current) {
   5366      modal.current.showModal();
   5367 
   5368      // Clear any previously selected radio button
   5369      const radioGroup = radioGroupRef.current;
   5370      if (radioGroup) {
   5371        const selectedRadioButton = radioGroup.querySelector("moz-radio[checked]");
   5372        if (selectedRadioButton) {
   5373          selectedRadioButton.removeAttribute("checked");
   5374        }
   5375      }
   5376 
   5377      // Clear out the states
   5378      setValueSelected(false);
   5379      setSelectedReason(null);
   5380    } else if (!report.visible && modal?.current?.open) {
   5381      modal.current.close();
   5382    }
   5383  }, [report.visible]);
   5384 
   5385  // Updates the submit button's state based on if a value is selected
   5386  (0,external_React_namespaceObject.useEffect)(() => {
   5387    const radioGroup = radioGroupRef.current;
   5388    const submitButton = submitButtonRef.current;
   5389    const handleRadioChange = e => {
   5390      const reasonValue = e?.target?.value;
   5391      if (reasonValue) {
   5392        setValueSelected(true);
   5393        setSelectedReason(reasonValue);
   5394      }
   5395    };
   5396    if (radioGroup) {
   5397      radioGroup.addEventListener("change", handleRadioChange);
   5398    }
   5399 
   5400    // Handle submit button state on valueSelected change
   5401    const updateSubmitState = () => {
   5402      if (valueSelected) {
   5403        submitButton.removeAttribute("disabled");
   5404      } else {
   5405        submitButton.setAttribute("disabled", "");
   5406      }
   5407    };
   5408    updateSubmitState();
   5409    return () => {
   5410      if (radioGroup) {
   5411        radioGroup.removeEventListener("change", handleRadioChange);
   5412      }
   5413    };
   5414  }, [valueSelected, selectedReason]);
   5415  return /*#__PURE__*/external_React_default().createElement("dialog", {
   5416    className: "report-content-form",
   5417    id: "dialog-report",
   5418    ref: modal,
   5419    onClose: () => dispatch({
   5420      type: actionTypes.REPORT_CLOSE
   5421    })
   5422  }, /*#__PURE__*/external_React_default().createElement("form", {
   5423    action: ""
   5424  }, report.card_type === "spoc" ? /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement("moz-radio-group", {
   5425    name: "report",
   5426    ref: radioGroupRef,
   5427    id: "report-group",
   5428    "data-l10n-id": "newtab-report-ads-why-reporting",
   5429    className: "report-ads-options",
   5430    headingLevel: "3"
   5431  }, /*#__PURE__*/external_React_default().createElement("moz-radio", {
   5432    "data-l10n-id": "newtab-report-ads-reason-not-interested",
   5433    value: "not_interested"
   5434  }), /*#__PURE__*/external_React_default().createElement("moz-radio", {
   5435    "data-l10n-id": "newtab-report-ads-reason-inappropriate",
   5436    value: "inappropriate"
   5437  }), /*#__PURE__*/external_React_default().createElement("moz-radio", {
   5438    "data-l10n-id": "newtab-report-ads-reason-seen-it-too-many-times",
   5439    value: "seen_too_many_times"
   5440  }))) : /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement("moz-radio-group", {
   5441    name: "report",
   5442    ref: radioGroupRef,
   5443    id: "report-group",
   5444    "data-l10n-id": "newtab-report-content-why-reporting-this",
   5445    className: "report-content-options",
   5446    headingLevel: "3"
   5447  }, /*#__PURE__*/external_React_default().createElement("moz-radio", {
   5448    "data-l10n-id": "newtab-report-content-wrong-category",
   5449    value: "wrong_category"
   5450  }), /*#__PURE__*/external_React_default().createElement("moz-radio", {
   5451    "data-l10n-id": "newtab-report-content-outdated",
   5452    value: "outdated"
   5453  }), /*#__PURE__*/external_React_default().createElement("moz-radio", {
   5454    "data-l10n-id": "newtab-report-content-inappropriate-offensive",
   5455    value: "inappropriate_or_offensive"
   5456  }), /*#__PURE__*/external_React_default().createElement("moz-radio", {
   5457    "data-l10n-id": "newtab-report-content-spam-misleading",
   5458    value: "spam_or_misleading"
   5459  }), /*#__PURE__*/external_React_default().createElement("moz-radio", {
   5460    "data-l10n-id": "newtab-report-content-requires-payment-subscription",
   5461    value: "requires_payment_or_subscription"
   5462  }, /*#__PURE__*/external_React_default().createElement("a", {
   5463    slot: "support-link",
   5464    is: "moz-support-link",
   5465    "support-page": "recommendations-firefox-new-tab#w_what-is-a-paywall",
   5466    "data-l10n-id": "newtab-report-content-requires-payment-subscription-learn-more",
   5467    rel: "noreferrer",
   5468    target: "_blank"
   5469  })))), /*#__PURE__*/external_React_default().createElement("moz-button-group", null, /*#__PURE__*/external_React_default().createElement("moz-button", {
   5470    "data-l10n-id": "newtab-report-cancel",
   5471    onClick: handleCancel,
   5472    className: "cancel-report-btn"
   5473  }), /*#__PURE__*/external_React_default().createElement("moz-button", {
   5474    type: "primary",
   5475    "data-l10n-id": "newtab-report-submit",
   5476    ref: submitButtonRef,
   5477    onClick: handleSubmit,
   5478    className: "submit-report-btn"
   5479  }))));
   5480 };
   5481 ;// CONCATENATED MODULE: ./content-src/lib/screenshot-utils.mjs
   5482 /* This Source Code Form is subject to the terms of the Mozilla Public
   5483 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   5484 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   5485 
   5486 /**
   5487 * List of helper functions for screenshot-based images.
   5488 *
   5489 * There are two kinds of images:
   5490 * 1. Remote Image: This is the image from the main process and it refers to
   5491 *    the image in the React props. This can either be an object with the `data`
   5492 *    and `path` properties, if it is a blob, or a string, if it is a normal image.
   5493 * 2. Local Image: This is the image object in the content process and it refers
   5494 *    to the image *object* in the React component's state. All local image
   5495 *    objects have the `url` property, and an additional property `path`, if they
   5496 *    are blobs.
   5497 */
   5498 const ScreenshotUtils = {
   5499  isBlob(isLocal, image) {
   5500    return !!(
   5501      image &&
   5502      image.path &&
   5503      ((!isLocal && image.data) || (isLocal && image.url))
   5504    );
   5505  },
   5506 
   5507  // This should always be called with a remote image and not a local image.
   5508  createLocalImageObject(remoteImage) {
   5509    if (!remoteImage) {
   5510      return null;
   5511    }
   5512    if (this.isBlob(false, remoteImage)) {
   5513      return {
   5514        url: globalThis.URL.createObjectURL(remoteImage.data),
   5515        path: remoteImage.path,
   5516      };
   5517    }
   5518    return { url: remoteImage };
   5519  },
   5520 
   5521  // Revokes the object URL of the image if the local image is a blob.
   5522  // This should always be called with a local image and not a remote image.
   5523  maybeRevokeBlobObjectURL(localImage) {
   5524    if (this.isBlob(true, localImage)) {
   5525      globalThis.URL.revokeObjectURL(localImage.url);
   5526    }
   5527  },
   5528 
   5529  // Checks if remoteImage and localImage are the same.
   5530  isRemoteImageLocal(localImage, remoteImage) {
   5531    // Both remoteImage and localImage are present.
   5532    if (remoteImage && localImage) {
   5533      return this.isBlob(false, remoteImage)
   5534        ? localImage.path === remoteImage.path
   5535        : localImage.url === remoteImage;
   5536    }
   5537 
   5538    // This will only handle the remaining three possible outcomes.
   5539    // (i.e. everything except when both image and localImage are present)
   5540    return !remoteImage && !localImage;
   5541  },
   5542 };
   5543 
   5544 ;// CONCATENATED MODULE: ./content-src/components/Card/Card.jsx
   5545 /* This Source Code Form is subject to the terms of the Mozilla Public
   5546 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   5547 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   5548 
   5549 
   5550 
   5551 
   5552 
   5553 
   5554 
   5555 
   5556 
   5557 // Keep track of pending image loads to only request once
   5558 const gImageLoading = new Map();
   5559 
   5560 /**
   5561 * Card component.
   5562 * Cards are found within a Section component and contain information about a link such
   5563 * as preview image, page title, page description, and some context about if the page
   5564 * was visited, bookmarked, trending etc...
   5565 * Each Section can make an unordered list of Cards which will create one instane of
   5566 * this class. Each card will then get a context menu which reflects the actions that
   5567 * can be done on this Card.
   5568 */
   5569 class _Card extends (external_React_default()).PureComponent {
   5570  constructor(props) {
   5571    super(props);
   5572    this.state = {
   5573      activeCard: null,
   5574      imageLoaded: false,
   5575      cardImage: null
   5576    };
   5577    this.onMenuButtonUpdate = this.onMenuButtonUpdate.bind(this);
   5578    this.onLinkClick = this.onLinkClick.bind(this);
   5579  }
   5580 
   5581  /**
   5582   * Helper to conditionally load an image and update state when it loads.
   5583   */
   5584  async maybeLoadImage() {
   5585    // No need to load if it's already loaded or no image
   5586    const {
   5587      cardImage
   5588    } = this.state;
   5589    if (!cardImage) {
   5590      return;
   5591    }
   5592    const imageUrl = cardImage.url;
   5593    if (!this.state.imageLoaded) {
   5594      // Initialize a promise to share a load across multiple card updates
   5595      if (!gImageLoading.has(imageUrl)) {
   5596        const loaderPromise = new Promise((resolve, reject) => {
   5597          const loader = new Image();
   5598          loader.addEventListener("load", resolve);
   5599          loader.addEventListener("error", reject);
   5600          loader.src = imageUrl;
   5601        });
   5602 
   5603        // Save and remove the promise only while it's pending
   5604        gImageLoading.set(imageUrl, loaderPromise);
   5605        loaderPromise.catch(ex => ex).then(() => gImageLoading.delete(imageUrl));
   5606      }
   5607 
   5608      // Wait for the image whether just started loading or reused promise
   5609      try {
   5610        await gImageLoading.get(imageUrl);
   5611      } catch (ex) {
   5612        // Ignore the failed image without changing state
   5613        return;
   5614      }
   5615 
   5616      // Only update state if we're still waiting to load the original image
   5617      if (ScreenshotUtils.isRemoteImageLocal(this.state.cardImage, this.props.link.image) && !this.state.imageLoaded) {
   5618        this.setState({
   5619          imageLoaded: true
   5620        });
   5621      }
   5622    }
   5623  }
   5624 
   5625  /**
   5626   * Helper to obtain the next state based on nextProps and prevState.
   5627   *
   5628   * NOTE: Rename this method to getDerivedStateFromProps when we update React
   5629   *       to >= 16.3. We will need to update tests as well. We cannot rename this
   5630   *       method to getDerivedStateFromProps now because there is a mismatch in
   5631   *       the React version that we are using for both testing and production.
   5632   *       (i.e. react-test-render => "16.3.2", react => "16.2.0").
   5633   *
   5634   * See https://github.com/airbnb/enzyme/blob/master/packages/enzyme-adapter-react-16/package.json#L43.
   5635   */
   5636  static getNextStateFromProps(nextProps, prevState) {
   5637    const {
   5638      image
   5639    } = nextProps.link;
   5640    const imageInState = ScreenshotUtils.isRemoteImageLocal(prevState.cardImage, image);
   5641    let nextState = null;
   5642 
   5643    // Image is updating.
   5644    if (!imageInState && nextProps.link) {
   5645      nextState = {
   5646        imageLoaded: false
   5647      };
   5648    }
   5649    if (imageInState) {
   5650      return nextState;
   5651    }
   5652 
   5653    // Since image was updated, attempt to revoke old image blob URL, if it exists.
   5654    ScreenshotUtils.maybeRevokeBlobObjectURL(prevState.cardImage);
   5655    nextState = nextState || {};
   5656    nextState.cardImage = ScreenshotUtils.createLocalImageObject(image);
   5657    return nextState;
   5658  }
   5659  onMenuButtonUpdate(isOpen) {
   5660    if (isOpen) {
   5661      this.setState({
   5662        activeCard: this.props.index
   5663      });
   5664    } else {
   5665      this.setState({
   5666        activeCard: null
   5667      });
   5668    }
   5669  }
   5670 
   5671  /**
   5672   * Report to telemetry additional information about the item.
   5673   */
   5674  _getTelemetryInfo() {
   5675    // Filter out "history" type for being the default
   5676    if (this.props.link.type !== "history") {
   5677      return {
   5678        value: {
   5679          card_type: this.props.link.type
   5680        }
   5681      };
   5682    }
   5683    return null;
   5684  }
   5685  onLinkClick(event) {
   5686    event.preventDefault();
   5687    const {
   5688      altKey,
   5689      button,
   5690      ctrlKey,
   5691      metaKey,
   5692      shiftKey
   5693    } = event;
   5694    if (this.props.link.type === "download") {
   5695      this.props.dispatch(actionCreators.OnlyToMain({
   5696        type: actionTypes.OPEN_DOWNLOAD_FILE,
   5697        data: Object.assign(this.props.link, {
   5698          event: {
   5699            button,
   5700            ctrlKey,
   5701            metaKey,
   5702            shiftKey
   5703          }
   5704        })
   5705      }));
   5706    } else {
   5707      this.props.dispatch(actionCreators.OnlyToMain({
   5708        type: actionTypes.OPEN_LINK,
   5709        data: Object.assign(this.props.link, {
   5710          event: {
   5711            altKey,
   5712            button,
   5713            ctrlKey,
   5714            metaKey,
   5715            shiftKey
   5716          }
   5717        })
   5718      }));
   5719    }
   5720    if (this.props.isWebExtension) {
   5721      this.props.dispatch(actionCreators.WebExtEvent(actionTypes.WEBEXT_CLICK, {
   5722        source: this.props.eventSource,
   5723        url: this.props.link.url,
   5724        action_position: this.props.index
   5725      }));
   5726    } else {
   5727      this.props.dispatch(actionCreators.UserEvent(Object.assign({
   5728        event: "CLICK",
   5729        source: this.props.eventSource,
   5730        action_position: this.props.index
   5731      }, this._getTelemetryInfo())));
   5732      if (this.props.shouldSendImpressionStats) {
   5733        this.props.dispatch(actionCreators.ImpressionStats({
   5734          source: this.props.eventSource,
   5735          click: 0,
   5736          tiles: [{
   5737            id: this.props.link.guid,
   5738            pos: this.props.index
   5739          }]
   5740        }));
   5741      }
   5742    }
   5743  }
   5744  componentDidMount() {
   5745    this.maybeLoadImage();
   5746  }
   5747  componentDidUpdate() {
   5748    this.maybeLoadImage();
   5749  }
   5750 
   5751  // NOTE: Remove this function when we update React to >= 16.3 since React will
   5752  //       call getDerivedStateFromProps automatically. We will also need to
   5753  //       rename getNextStateFromProps to getDerivedStateFromProps.
   5754  componentWillMount() {
   5755    const nextState = _Card.getNextStateFromProps(this.props, this.state);
   5756    if (nextState) {
   5757      this.setState(nextState);
   5758    }
   5759  }
   5760 
   5761  // NOTE: Remove this function when we update React to >= 16.3 since React will
   5762  //       call getDerivedStateFromProps automatically. We will also need to
   5763  //       rename getNextStateFromProps to getDerivedStateFromProps.
   5764  componentWillReceiveProps(nextProps) {
   5765    const nextState = _Card.getNextStateFromProps(nextProps, this.state);
   5766    if (nextState) {
   5767      this.setState(nextState);
   5768    }
   5769  }
   5770  componentWillUnmount() {
   5771    ScreenshotUtils.maybeRevokeBlobObjectURL(this.state.cardImage);
   5772  }
   5773  render() {
   5774    const {
   5775      index,
   5776      className,
   5777      link,
   5778      dispatch,
   5779      contextMenuOptions,
   5780      eventSource,
   5781      shouldSendImpressionStats
   5782    } = this.props;
   5783    const {
   5784      props
   5785    } = this;
   5786    const title = link.title || link.hostname;
   5787    const isContextMenuOpen = this.state.activeCard === index;
   5788    // Display "now" as "trending" until we have new strings #3402
   5789    const {
   5790      icon,
   5791      fluentID
   5792    } = cardContextTypes[link.type === "now" ? "trending" : link.type] || {};
   5793    const hasImage = this.state.cardImage || link.hasImage;
   5794    const imageStyle = {
   5795      backgroundImage: this.state.cardImage ? `url(${this.state.cardImage.url})` : "none"
   5796    };
   5797    const outerClassName = ["card-outer", className, isContextMenuOpen && "active", props.placeholder && "placeholder"].filter(v => v).join(" ");
   5798    return /*#__PURE__*/external_React_default().createElement("li", {
   5799      className: outerClassName
   5800    }, /*#__PURE__*/external_React_default().createElement("a", {
   5801      href: link.type === "pocket" ? link.open_url : link.url,
   5802      onClick: !props.placeholder ? this.onLinkClick : undefined
   5803    }, /*#__PURE__*/external_React_default().createElement("div", {
   5804      className: "card"
   5805    }, /*#__PURE__*/external_React_default().createElement("div", {
   5806      className: "card-preview-image-outer"
   5807    }, hasImage && /*#__PURE__*/external_React_default().createElement("div", {
   5808      className: `card-preview-image${this.state.imageLoaded ? " loaded" : ""}`,
   5809      style: imageStyle
   5810    })), /*#__PURE__*/external_React_default().createElement("div", {
   5811      className: "card-details"
   5812    }, link.type === "download" && /*#__PURE__*/external_React_default().createElement("div", {
   5813      className: "card-host-name alternate",
   5814      "data-l10n-id": "newtab-menu-open-file"
   5815    }), link.hostname && /*#__PURE__*/external_React_default().createElement("div", {
   5816      className: "card-host-name"
   5817    }, link.hostname.slice(0, 100), link.type === "download" && `  \u2014 ${link.description}`), /*#__PURE__*/external_React_default().createElement("div", {
   5818      className: ["card-text", icon ? "" : "no-context", link.description ? "" : "no-description", link.hostname ? "" : "no-host-name"].join(" ")
   5819    }, /*#__PURE__*/external_React_default().createElement("h4", {
   5820      className: "card-title",
   5821      dir: "auto"
   5822    }, link.title), /*#__PURE__*/external_React_default().createElement("p", {
   5823      className: "card-description",
   5824      dir: "auto"
   5825    }, link.description)), /*#__PURE__*/external_React_default().createElement("div", {
   5826      className: "card-context"
   5827    }, icon && !link.context && /*#__PURE__*/external_React_default().createElement("span", {
   5828      "aria-haspopup": "true",
   5829      className: `card-context-icon icon icon-${icon}`
   5830    }), link.icon && link.context && /*#__PURE__*/external_React_default().createElement("span", {
   5831      "aria-haspopup": "true",
   5832      className: "card-context-icon icon",
   5833      style: {
   5834        backgroundImage: `url('${link.icon}')`
   5835      }
   5836    }), fluentID && !link.context && /*#__PURE__*/external_React_default().createElement("div", {
   5837      className: "card-context-label",
   5838      "data-l10n-id": fluentID
   5839    }), link.context && /*#__PURE__*/external_React_default().createElement("div", {
   5840      className: "card-context-label"
   5841    }, link.context))))), !props.placeholder && /*#__PURE__*/external_React_default().createElement(ContextMenuButton, {
   5842      tooltip: "newtab-menu-content-tooltip",
   5843      tooltipArgs: {
   5844        title
   5845      },
   5846      onUpdate: this.onMenuButtonUpdate
   5847    }, /*#__PURE__*/external_React_default().createElement(LinkMenu, {
   5848      dispatch: dispatch,
   5849      index: index,
   5850      source: eventSource,
   5851      options: link.contextMenuOptions || contextMenuOptions,
   5852      site: link,
   5853      siteInfo: this._getTelemetryInfo(),
   5854      shouldSendImpressionStats: shouldSendImpressionStats
   5855    })));
   5856  }
   5857 }
   5858 _Card.defaultProps = {
   5859  link: {}
   5860 };
   5861 const Card = (0,external_ReactRedux_namespaceObject.connect)(state => ({
   5862  platform: state.Prefs.values.platform
   5863 }))(_Card);
   5864 const PlaceholderCard = props => /*#__PURE__*/external_React_default().createElement(Card, {
   5865  placeholder: true,
   5866  className: props.className
   5867 });
   5868 ;// CONCATENATED MODULE: ./content-src/lib/perf-service.mjs
   5869 /* This Source Code Form is subject to the terms of the Mozilla Public
   5870 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   5871 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   5872 
   5873 let usablePerfObj = window.performance;
   5874 
   5875 function _PerfService(options) {
   5876  // For testing, so that we can use a fake Window.performance object with
   5877  // known state.
   5878  if (options && options.performanceObj) {
   5879    this._perf = options.performanceObj;
   5880  } else {
   5881    this._perf = usablePerfObj;
   5882  }
   5883 }
   5884 
   5885 _PerfService.prototype = {
   5886  /**
   5887   * Calls the underlying mark() method on the appropriate Window.performance
   5888   * object to add a mark with the given name to the appropriate performance
   5889   * timeline.
   5890   *
   5891   * @param  {string} name  the name to give the current mark
   5892   * @return {void}
   5893   */
   5894  mark: function mark(str) {
   5895    this._perf.mark(str);
   5896  },
   5897 
   5898  /**
   5899   * Calls the underlying getEntriesByName on the appropriate Window.performance
   5900   * object.
   5901   *
   5902   * @param  {string} name
   5903   * @param  {string} type eg "mark"
   5904   * @return {Array}       Performance* objects
   5905   */
   5906  getEntriesByName: function getEntriesByName(entryName, type) {
   5907    return this._perf.getEntriesByName(entryName, type);
   5908  },
   5909 
   5910  /**
   5911   * The timeOrigin property from the appropriate performance object.
   5912   * Used to ensure that timestamps from the add-on code and the content code
   5913   * are comparable.
   5914   *
   5915   * Note: If this is called from a context without a window
   5916   * (eg a JSM in chrome), it will return the timeOrigin of the XUL hidden
   5917   * window, which appears to be the first created window (and thus
   5918   * timeOrigin) in the browser.  Note also, however, there is also a private
   5919   * hidden window, presumably for private browsing, which appears to be
   5920   * created dynamically later.  Exactly how/when that shows up needs to be
   5921   * investigated.
   5922   *
   5923   * @return {number} A double of milliseconds with a precision of 0.5us.
   5924   */
   5925  get timeOrigin() {
   5926    return this._perf.timeOrigin;
   5927  },
   5928 
   5929  /**
   5930   * Returns the "absolute" version of performance.now(), i.e. one that
   5931   * should ([bug 1401406](https://bugzilla.mozilla.org/show_bug.cgi?id=1401406)
   5932   * be comparable across both chrome and content.
   5933   *
   5934   * @return {number}
   5935   */
   5936  absNow: function absNow() {
   5937    return this.timeOrigin + this._perf.now();
   5938  },
   5939 
   5940  /**
   5941   * This returns the absolute startTime from the most recent performance.mark()
   5942   * with the given name.
   5943   *
   5944   * @param  {string} name  the name to lookup the start time for
   5945   *
   5946   * @return {number}       the returned start time, as a DOMHighResTimeStamp
   5947   *
   5948   * @throws {Error}        "No Marks with the name ..." if none are available
   5949   *
   5950   * Note: Always surround calls to this by try/catch.  Otherwise your code
   5951   * may fail when the `privacy.resistFingerprinting` pref is true.  When
   5952   * this pref is set, all attempts to get marks will likely fail, which will
   5953   * cause this method to throw.
   5954   *
   5955   * See [bug 1369303](https://bugzilla.mozilla.org/show_bug.cgi?id=1369303)
   5956   * for more info.
   5957   */
   5958  getMostRecentAbsMarkStartByName(entryName) {
   5959    let entries = this.getEntriesByName(entryName, "mark");
   5960 
   5961    if (!entries.length) {
   5962      throw new Error(`No marks with the name ${entryName}`);
   5963    }
   5964 
   5965    let mostRecentEntry = entries[entries.length - 1];
   5966    return this._perf.timeOrigin + mostRecentEntry.startTime;
   5967  },
   5968 };
   5969 
   5970 const perfService = new _PerfService();
   5971 
   5972 ;// CONCATENATED MODULE: ./content-src/components/ComponentPerfTimer/ComponentPerfTimer.jsx
   5973 /* This Source Code Form is subject to the terms of the Mozilla Public
   5974 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   5975 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   5976 
   5977 
   5978 
   5979 
   5980 
   5981 // Currently record only a fixed set of sections. This will prevent data
   5982 // from custom sections from showing up or from topstories.
   5983 const RECORDED_SECTIONS = ["highlights", "topsites"];
   5984 class ComponentPerfTimer extends (external_React_default()).Component {
   5985  constructor(props) {
   5986    super(props);
   5987    // Just for test dependency injection:
   5988    this.perfSvc = this.props.perfSvc || perfService;
   5989    this._sendBadStateEvent = this._sendBadStateEvent.bind(this);
   5990    this._sendPaintedEvent = this._sendPaintedEvent.bind(this);
   5991    this._reportMissingData = false;
   5992    this._timestampHandled = false;
   5993    this._recordedFirstRender = false;
   5994  }
   5995  componentDidMount() {
   5996    if (!RECORDED_SECTIONS.includes(this.props.id)) {
   5997      return;
   5998    }
   5999    this._maybeSendPaintedEvent();
   6000  }
   6001  componentDidUpdate() {
   6002    if (!RECORDED_SECTIONS.includes(this.props.id)) {
   6003      return;
   6004    }
   6005    this._maybeSendPaintedEvent();
   6006  }
   6007 
   6008  /**
   6009   * Call the given callback after the upcoming frame paints.
   6010   *
   6011   * Note: Both setTimeout and requestAnimationFrame are throttled when the page
   6012   * is hidden, so this callback may get called up to a second or so after the
   6013   * requestAnimationFrame "paint" for hidden tabs.
   6014   *
   6015   * Newtabs hidden while loading will presumably be fairly rare (other than
   6016   * preloaded tabs, which we will be filtering out on the server side), so such
   6017   * cases should get lost in the noise.
   6018   *
   6019   * If we decide that it's important to find out when something that's hidden
   6020   * has "painted", however, another option is to post a message to this window.
   6021   * That should happen even faster than setTimeout, and, at least as of this
   6022   * writing, it's not throttled in hidden windows in Firefox.
   6023   *
   6024   * @param {Function} callback
   6025   *
   6026   * @returns void
   6027   */
   6028  _afterFramePaint(callback) {
   6029    requestAnimationFrame(() => setTimeout(callback, 0));
   6030  }
   6031  _maybeSendBadStateEvent() {
   6032    // Follow up bugs:
   6033    // https://github.com/mozilla/activity-stream/issues/3691
   6034    if (!this.props.initialized) {
   6035      // Remember to report back when data is available.
   6036      this._reportMissingData = true;
   6037    } else if (this._reportMissingData) {
   6038      this._reportMissingData = false;
   6039      // Report how long it took for component to become initialized.
   6040      this._sendBadStateEvent();
   6041    }
   6042  }
   6043  _maybeSendPaintedEvent() {
   6044    // If we've already handled a timestamp, don't do it again.
   6045    if (this._timestampHandled || !this.props.initialized) {
   6046      return;
   6047    }
   6048 
   6049    // And if we haven't, we're doing so now, so remember that. Even if
   6050    // something goes wrong in the callback, we can't try again, as we'd be
   6051    // sending back the wrong data, and we have to do it here, so that other
   6052    // calls to this method while waiting for the next frame won't also try to
   6053    // handle it.
   6054    this._timestampHandled = true;
   6055    this._afterFramePaint(this._sendPaintedEvent);
   6056  }
   6057 
   6058  /**
   6059   * Triggered by call to render. Only first call goes through due to
   6060   * `_recordedFirstRender`.
   6061   */
   6062  _ensureFirstRenderTsRecorded() {
   6063    // Used as t0 for recording how long component took to initialize.
   6064    if (!this._recordedFirstRender) {
   6065      this._recordedFirstRender = true;
   6066      // topsites_first_render_ts, highlights_first_render_ts.
   6067      const key = `${this.props.id}_first_render_ts`;
   6068      this.perfSvc.mark(key);
   6069    }
   6070  }
   6071 
   6072  /**
   6073   * Creates `SAVE_SESSION_PERF_DATA` with timestamp in ms
   6074   * of how much longer the data took to be ready for display than it would
   6075   * have been the ideal case.
   6076   * https://github.com/mozilla/ping-centre/issues/98
   6077   */
   6078  _sendBadStateEvent() {
   6079    // highlights_data_ready_ts, topsites_data_ready_ts.
   6080    const dataReadyKey = `${this.props.id}_data_ready_ts`;
   6081    this.perfSvc.mark(dataReadyKey);
   6082    try {
   6083      const firstRenderKey = `${this.props.id}_first_render_ts`;
   6084      // value has to be Int32.
   6085      const value = parseInt(this.perfSvc.getMostRecentAbsMarkStartByName(dataReadyKey) - this.perfSvc.getMostRecentAbsMarkStartByName(firstRenderKey), 10);
   6086      this.props.dispatch(actionCreators.OnlyToMain({
   6087        type: actionTypes.SAVE_SESSION_PERF_DATA,
   6088        // highlights_data_late_by_ms, topsites_data_late_by_ms.
   6089        data: {
   6090          [`${this.props.id}_data_late_by_ms`]: value
   6091        }
   6092      }));
   6093    } catch (ex) {
   6094      // If this failed, it's likely because the `privacy.resistFingerprinting`
   6095      // pref is true.
   6096    }
   6097  }
   6098  _sendPaintedEvent() {
   6099    // Record first_painted event but only send if topsites.
   6100    if (this.props.id !== "topsites") {
   6101      return;
   6102    }
   6103 
   6104    // topsites_first_painted_ts.
   6105    const key = `${this.props.id}_first_painted_ts`;
   6106    this.perfSvc.mark(key);
   6107    try {
   6108      const data = {};
   6109      data[key] = this.perfSvc.getMostRecentAbsMarkStartByName(key);
   6110      this.props.dispatch(actionCreators.OnlyToMain({
   6111        type: actionTypes.SAVE_SESSION_PERF_DATA,
   6112        data
   6113      }));
   6114    } catch (ex) {
   6115      // If this failed, it's likely because the `privacy.resistFingerprinting`
   6116      // pref is true.  We should at least not blow up, and should continue
   6117      // to set this._timestampHandled to avoid going through this again.
   6118    }
   6119  }
   6120  render() {
   6121    if (RECORDED_SECTIONS.includes(this.props.id)) {
   6122      this._ensureFirstRenderTsRecorded();
   6123      this._maybeSendBadStateEvent();
   6124    }
   6125    return this.props.children;
   6126  }
   6127 }
   6128 ;// CONCATENATED MODULE: ./content-src/components/MoreRecommendations/MoreRecommendations.jsx
   6129 /* This Source Code Form is subject to the terms of the Mozilla Public
   6130 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   6131 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   6132 
   6133 
   6134 class MoreRecommendations extends (external_React_default()).PureComponent {
   6135  render() {
   6136    const {
   6137      read_more_endpoint
   6138    } = this.props;
   6139    if (read_more_endpoint) {
   6140      return /*#__PURE__*/external_React_default().createElement("a", {
   6141        className: "more-recommendations",
   6142        href: read_more_endpoint,
   6143        "data-l10n-id": "newtab-pocket-more-recommendations"
   6144      });
   6145    }
   6146    return null;
   6147  }
   6148 }
   6149 ;// CONCATENATED MODULE: ./content-src/components/ModalOverlay/ModalOverlay.jsx
   6150 /* This Source Code Form is subject to the terms of the Mozilla Public
   6151 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   6152 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   6153 
   6154 
   6155 function ModalOverlayWrapper({
   6156  // eslint-disable-next-line no-shadow
   6157  document = globalThis.document,
   6158  unstyled,
   6159  innerClassName,
   6160  onClose,
   6161  children,
   6162  headerId,
   6163  id
   6164 }) {
   6165  const modalRef = (0,external_React_namespaceObject.useRef)(null);
   6166  let className = unstyled ? "" : "modalOverlayInner active";
   6167  if (innerClassName) {
   6168    className += ` ${innerClassName}`;
   6169  }
   6170 
   6171  // The intended behaviour is to listen for an escape key
   6172  // but not for a click; see Bug 1582242
   6173  const onKeyDown = (0,external_React_namespaceObject.useCallback)(event => {
   6174    if (event.key === "Escape") {
   6175      onClose(event);
   6176    }
   6177  }, [onClose]);
   6178  (0,external_React_namespaceObject.useEffect)(() => {
   6179    document.addEventListener("keydown", onKeyDown);
   6180    document.body.classList.add("modal-open");
   6181    return () => {
   6182      document.removeEventListener("keydown", onKeyDown);
   6183      document.body.classList.remove("modal-open");
   6184    };
   6185  }, [document, onKeyDown]);
   6186  return /*#__PURE__*/external_React_default().createElement("div", {
   6187    className: "modalOverlayOuter active",
   6188    onKeyDown: onKeyDown,
   6189    role: "presentation"
   6190  }, /*#__PURE__*/external_React_default().createElement("div", {
   6191    className: className,
   6192    "aria-labelledby": headerId,
   6193    id: id,
   6194    role: "dialog",
   6195    ref: modalRef
   6196  }, children));
   6197 }
   6198 
   6199 ;// CONCATENATED MODULE: ./content-src/components/TopSites/SearchShortcutsForm.jsx
   6200 /* This Source Code Form is subject to the terms of the Mozilla Public
   6201 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   6202 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   6203 
   6204 
   6205 
   6206 
   6207 class SelectableSearchShortcut extends (external_React_default()).PureComponent {
   6208  render() {
   6209    const {
   6210      shortcut,
   6211      selected
   6212    } = this.props;
   6213    const imageStyle = {
   6214      backgroundImage: `url("${shortcut.tippyTopIcon}")`
   6215    };
   6216    return /*#__PURE__*/external_React_default().createElement("div", {
   6217      className: "top-site-outer search-shortcut"
   6218    }, /*#__PURE__*/external_React_default().createElement("input", {
   6219      type: "checkbox",
   6220      id: shortcut.keyword,
   6221      name: shortcut.keyword,
   6222      checked: selected,
   6223      onChange: this.props.onChange
   6224    }), /*#__PURE__*/external_React_default().createElement("label", {
   6225      htmlFor: shortcut.keyword
   6226    }, /*#__PURE__*/external_React_default().createElement("div", {
   6227      className: "top-site-inner"
   6228    }, /*#__PURE__*/external_React_default().createElement("span", null, /*#__PURE__*/external_React_default().createElement("div", {
   6229      className: "tile"
   6230    }, /*#__PURE__*/external_React_default().createElement("div", {
   6231      className: "top-site-icon rich-icon",
   6232      style: imageStyle,
   6233      "data-fallback": "@"
   6234    }), /*#__PURE__*/external_React_default().createElement("div", {
   6235      className: "top-site-icon search-topsite"
   6236    })), /*#__PURE__*/external_React_default().createElement("div", {
   6237      className: "title"
   6238    }, /*#__PURE__*/external_React_default().createElement("span", {
   6239      dir: "auto"
   6240    }, shortcut.keyword))))));
   6241  }
   6242 }
   6243 class SearchShortcutsForm extends (external_React_default()).PureComponent {
   6244  constructor(props) {
   6245    super(props);
   6246    this.handleChange = this.handleChange.bind(this);
   6247    this.onCancelButtonClick = this.onCancelButtonClick.bind(this);
   6248    this.onSaveButtonClick = this.onSaveButtonClick.bind(this);
   6249 
   6250    // clone the shortcuts and add them to the state so we can add isSelected property
   6251    const shortcuts = [];
   6252    const {
   6253      rows,
   6254      searchShortcuts
   6255    } = props.TopSites;
   6256    searchShortcuts.forEach(shortcut => {
   6257      shortcuts.push({
   6258        ...shortcut,
   6259        isSelected: !!rows.find(row => row && row.isPinned && row.searchTopSite && row.label === shortcut.keyword)
   6260      });
   6261    });
   6262    this.state = {
   6263      shortcuts
   6264    };
   6265  }
   6266  handleChange(event) {
   6267    const {
   6268      target
   6269    } = event;
   6270    const {
   6271      name: targetName,
   6272      checked
   6273    } = target;
   6274    this.setState(prevState => {
   6275      const shortcuts = prevState.shortcuts.slice();
   6276      let shortcut = shortcuts.find(({
   6277        keyword
   6278      }) => keyword === targetName);
   6279      shortcut.isSelected = checked;
   6280      return {
   6281        shortcuts
   6282      };
   6283    });
   6284  }
   6285  onCancelButtonClick(ev) {
   6286    ev.preventDefault();
   6287    this.props.onClose();
   6288  }
   6289  onSaveButtonClick(ev) {
   6290    ev.preventDefault();
   6291 
   6292    // Check if there were any changes and act accordingly
   6293    const {
   6294      rows
   6295    } = this.props.TopSites;
   6296    const pinQueue = [];
   6297    const unpinQueue = [];
   6298    this.state.shortcuts.forEach(shortcut => {
   6299      const alreadyPinned = rows.find(row => row && row.isPinned && row.searchTopSite && row.label === shortcut.keyword);
   6300      if (shortcut.isSelected && !alreadyPinned) {
   6301        pinQueue.push(this._searchTopSite(shortcut));
   6302      } else if (!shortcut.isSelected && alreadyPinned) {
   6303        unpinQueue.push({
   6304          url: alreadyPinned.url,
   6305          searchVendor: shortcut.shortURL
   6306        });
   6307      }
   6308    });
   6309 
   6310    // Tell the feed to do the work.
   6311    this.props.dispatch(actionCreators.OnlyToMain({
   6312      type: actionTypes.UPDATE_PINNED_SEARCH_SHORTCUTS,
   6313      data: {
   6314        addedShortcuts: pinQueue,
   6315        deletedShortcuts: unpinQueue
   6316      }
   6317    }));
   6318 
   6319    // Send the Telemetry pings.
   6320    pinQueue.forEach(shortcut => {
   6321      this.props.dispatch(actionCreators.UserEvent({
   6322        source: TOP_SITES_SOURCE,
   6323        event: "SEARCH_EDIT_ADD",
   6324        value: {
   6325          search_vendor: shortcut.searchVendor
   6326        }
   6327      }));
   6328    });
   6329    unpinQueue.forEach(shortcut => {
   6330      this.props.dispatch(actionCreators.UserEvent({
   6331        source: TOP_SITES_SOURCE,
   6332        event: "SEARCH_EDIT_DELETE",
   6333        value: {
   6334          search_vendor: shortcut.searchVendor
   6335        }
   6336      }));
   6337    });
   6338    this.props.onClose();
   6339  }
   6340  _searchTopSite(shortcut) {
   6341    return {
   6342      url: shortcut.url,
   6343      searchTopSite: true,
   6344      label: shortcut.keyword,
   6345      searchVendor: shortcut.shortURL
   6346    };
   6347  }
   6348  render() {
   6349    return /*#__PURE__*/external_React_default().createElement("form", {
   6350      className: "topsite-form"
   6351    }, /*#__PURE__*/external_React_default().createElement("div", {
   6352      className: "search-shortcuts-container"
   6353    }, /*#__PURE__*/external_React_default().createElement("h3", {
   6354      className: "section-title grey-title",
   6355      "data-l10n-id": "newtab-topsites-add-search-engine-header"
   6356    }), /*#__PURE__*/external_React_default().createElement("div", null, this.state.shortcuts.map(shortcut => /*#__PURE__*/external_React_default().createElement(SelectableSearchShortcut, {
   6357      key: shortcut.keyword,
   6358      shortcut: shortcut,
   6359      selected: shortcut.isSelected,
   6360      onChange: this.handleChange
   6361    })))), /*#__PURE__*/external_React_default().createElement("section", {
   6362      className: "actions"
   6363    }, /*#__PURE__*/external_React_default().createElement("button", {
   6364      className: "cancel",
   6365      type: "button",
   6366      onClick: this.onCancelButtonClick,
   6367      "data-l10n-id": "newtab-topsites-cancel-button"
   6368    }), /*#__PURE__*/external_React_default().createElement("button", {
   6369      className: "done",
   6370      type: "submit",
   6371      onClick: this.onSaveButtonClick,
   6372      "data-l10n-id": "newtab-topsites-save-button"
   6373    })));
   6374  }
   6375 }
   6376 ;// CONCATENATED MODULE: ../../modules/Dedupe.sys.mjs
   6377 /* This Source Code Form is subject to the terms of the Mozilla Public
   6378 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   6379 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   6380 
   6381 class Dedupe {
   6382  constructor(createKey) {
   6383    this.createKey = createKey || this.defaultCreateKey;
   6384  }
   6385 
   6386  defaultCreateKey(item) {
   6387    return item;
   6388  }
   6389 
   6390  /**
   6391   * Dedupe any number of grouped elements favoring those from earlier groups.
   6392   *
   6393   * @param {Array} groups Contains an arbitrary number of arrays of elements.
   6394   * @returns {Array} A matching array of each provided group deduped.
   6395   */
   6396  group(...groups) {
   6397    const globalKeys = new Set();
   6398    const result = [];
   6399    for (const values of groups) {
   6400      const valueMap = new Map();
   6401      for (const value of values) {
   6402        const key = this.createKey(value);
   6403        if (!globalKeys.has(key) && !valueMap.has(key)) {
   6404          valueMap.set(key, value);
   6405        }
   6406      }
   6407      result.push(valueMap);
   6408      valueMap.forEach((value, key) => globalKeys.add(key));
   6409    }
   6410    return result.map(m => Array.from(m.values()));
   6411  }
   6412 }
   6413 
   6414 ;// CONCATENATED MODULE: ../../components/topsites/constants.mjs
   6415 /* This Source Code Form is subject to the terms of the Mozilla Public
   6416 * License, v. 2.0. If a copy of the MPL was not distributed with this
   6417 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
   6418 
   6419 const TOP_SITES_DEFAULT_ROWS = 1;
   6420 const TOP_SITES_MAX_SITES_PER_ROW = 8;
   6421 
   6422 ;// CONCATENATED MODULE: ./common/Reducers.sys.mjs
   6423 /* This Source Code Form is subject to the terms of the Mozilla Public
   6424 * License, v. 2.0. If a copy of the MPL was not distributed with this
   6425 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
   6426 
   6427 
   6428 
   6429 
   6430 
   6431 
   6432 const dedupe = new Dedupe(site => site && site.url);
   6433 
   6434 const INITIAL_STATE = {
   6435  App: {
   6436    // Have we received real data from the app yet?
   6437    initialized: false,
   6438    locale: "",
   6439    isForStartupCache: {
   6440      App: false,
   6441      TopSites: false,
   6442      DiscoveryStream: false,
   6443      Weather: false,
   6444      Wallpaper: false,
   6445    },
   6446    customizeMenuVisible: false,
   6447  },
   6448  Ads: {
   6449    initialized: false,
   6450    lastUpdated: null,
   6451    tiles: {},
   6452    spocs: {},
   6453    spocPlacements: {},
   6454  },
   6455  TopSites: {
   6456    // Have we received real data from history yet?
   6457    initialized: false,
   6458    // The history (and possibly default) links
   6459    rows: [],
   6460    // Used in content only to dispatch action to TopSiteForm.
   6461    editForm: null,
   6462    // Used in content only to open the SearchShortcutsForm modal.
   6463    showSearchShortcutsForm: false,
   6464    // The list of available search shortcuts.
   6465    searchShortcuts: [],
   6466    // The "Share-of-Voice" allocations generated by TopSitesFeed
   6467    sov: {
   6468      ready: false,
   6469      positions: [
   6470        // {position: 0, assignedPartner: "amp"},
   6471        // {position: 1, assignedPartner: "moz-sales"},
   6472      ],
   6473    },
   6474  },
   6475  Prefs: {
   6476    initialized: false,
   6477    values: { featureConfig: {} },
   6478  },
   6479  Dialog: {
   6480    visible: false,
   6481    data: {},
   6482  },
   6483  Sections: [],
   6484  Pocket: {
   6485    pocketCta: {},
   6486    waitingForSpoc: true,
   6487  },
   6488  // This is the new pocket configurable layout state.
   6489  DiscoveryStream: {
   6490    // This is a JSON-parsed copy of the discoverystream.config pref value.
   6491    config: { enabled: false },
   6492    layout: [],
   6493    topicsLoading: false,
   6494    feeds: {
   6495      data: {
   6496        // "https://foo.com/feed1": {lastUpdated: 123, data: [], personalized: false}
   6497      },
   6498      loaded: false,
   6499    },
   6500    // Used to show impressions in newtab devtools.
   6501    impressions: {
   6502      feed: {},
   6503    },
   6504    // Used to show blocks in newtab devtools.
   6505    blocks: {},
   6506    spocs: {
   6507      spocs_endpoint: "",
   6508      lastUpdated: null,
   6509      cacheUpdateTime: null,
   6510      onDemand: {
   6511        enabled: false,
   6512        loaded: false,
   6513      },
   6514      data: {
   6515        // "spocs": {title: "", context: "", items: [], personalized: false},
   6516        // "placement1": {title: "", context: "", items: [], personalized: false},
   6517      },
   6518      loaded: false,
   6519      frequency_caps: [],
   6520      blocked: [],
   6521      placements: [],
   6522    },
   6523    experimentData: {
   6524      utmSource: "pocket-newtab",
   6525      utmCampaign: undefined,
   6526      utmContent: undefined,
   6527    },
   6528    showTopicSelection: false,
   6529    report: {
   6530      visible: false,
   6531      data: {},
   6532    },
   6533    sectionPersonalization: {},
   6534  },
   6535  // Messages received from ASRouter to render in newtab
   6536  Messages: {
   6537    // messages received from ASRouter are initially visible
   6538    isVisible: true,
   6539    // portID for that tab that was sent the message
   6540    portID: "",
   6541    // READONLY Message data received from ASRouter
   6542    messageData: {},
   6543  },
   6544  Notifications: {
   6545    showNotifications: false,
   6546    toastCounter: 0,
   6547    toastId: "",
   6548    // This queue is reset each time SHOW_TOAST_MESSAGE is ran.
   6549    // For can be a queue in the future, but for now is one item
   6550    toastQueue: [],
   6551  },
   6552  Personalization: {
   6553    lastUpdated: null,
   6554    initialized: false,
   6555  },
   6556  InferredPersonalization: {
   6557    initialized: false,
   6558    lastUpdated: null,
   6559    inferredInterests: {},
   6560    coarseInferredInterests: {},
   6561    coarsePrivateInferredInterests: {},
   6562  },
   6563  Search: {
   6564    // When search hand-off is enabled, we render a big button that is styled to
   6565    // look like a search textbox. If the button is clicked, we style
   6566    // the button as if it was a focused search box and show a fake cursor but
   6567    // really focus the awesomebar without the focus styles ("hidden focus").
   6568    fakeFocus: false,
   6569    // Hide the search box after handing off to AwesomeBar and user starts typing.
   6570    hide: false,
   6571  },
   6572  Wallpapers: {
   6573    wallpaperList: [],
   6574    highlightSeenCounter: 0,
   6575    categories: [],
   6576    uploadedWallpaper: "",
   6577  },
   6578  Weather: {
   6579    initialized: false,
   6580    lastUpdated: null,
   6581    query: "",
   6582    suggestions: [],
   6583    locationData: {
   6584      city: "",
   6585      adminArea: "",
   6586      country: "",
   6587    },
   6588    // Display search input in Weather widget
   6589    searchActive: false,
   6590    locationSearchString: "",
   6591    suggestedLocations: [],
   6592  },
   6593  // Widgets
   6594  ListsWidget: {
   6595    // value pointing to last selectled list
   6596    selected: "taskList",
   6597    // Default state of an empty task list
   6598    lists: {
   6599      taskList: {
   6600        label: "",
   6601        tasks: [],
   6602        completed: [],
   6603      },
   6604    },
   6605  },
   6606  TimerWidget: {
   6607    // The timer will have 2 types of states, focus and break.
   6608    // Focus will the default state
   6609    timerType: "focus",
   6610    focus: {
   6611      // Timer duration set by user; 25 mins by default
   6612      duration: 25 * 60,
   6613      // Initial duration - also set by the user; does not update until timer ends or user resets timer
   6614      initialDuration: 25 * 60,
   6615      // the Date.now() value when a user starts/resumes a timer
   6616      startTime: null,
   6617      // Boolean indicating if timer is currently running
   6618      isRunning: false,
   6619    },
   6620    break: {
   6621      duration: 5 * 60,
   6622      initialDuration: 5 * 60,
   6623      startTime: null,
   6624      isRunning: false,
   6625    },
   6626  },
   6627  ExternalComponents: {
   6628    components: [],
   6629  },
   6630 };
   6631 
   6632 function App(prevState = INITIAL_STATE.App, action) {
   6633  switch (action.type) {
   6634    case actionTypes.INIT:
   6635      return Object.assign({}, prevState, action.data || {}, {
   6636        initialized: true,
   6637      });
   6638    case actionTypes.TOP_SITES_UPDATED:
   6639      // Toggle `isForStartupCache.TopSites` when receiving the `TOP_SITES_UPDATE` action
   6640      // so that sponsored tiles can be rendered as usual. See Bug 1826360.
   6641      return {
   6642        ...prevState,
   6643        isForStartupCache: { ...prevState.isForStartupCache, TopSites: false },
   6644      };
   6645    case actionTypes.DISCOVERY_STREAM_SPOCS_UPDATE:
   6646      // Toggle `isForStartupCache.DiscoveryStream` when receiving the `DISCOVERY_STREAM_SPOCS_UPDATE` action
   6647      // so that spoc cards can be rendered as usual.
   6648      return {
   6649        ...prevState,
   6650        isForStartupCache: {
   6651          ...prevState.isForStartupCache,
   6652          DiscoveryStream: false,
   6653        },
   6654      };
   6655    case actionTypes.WEATHER_UPDATE:
   6656      // Toggle `isForStartupCache.Weather` when receiving the `WEATHER_UPDATE` action
   6657      // so that weather can be rendered as usual.
   6658      return {
   6659        ...prevState,
   6660        isForStartupCache: { ...prevState.isForStartupCache, Weather: false },
   6661      };
   6662    case actionTypes.WALLPAPERS_CUSTOM_SET:
   6663      // Toggle `isForStartupCache.Wallpaper` when receiving the `WALLPAPERS_CUSTOM_SET` action
   6664      // so that custom wallpaper can be rendered as usual.
   6665      return {
   6666        ...prevState,
   6667        isForStartupCache: { ...prevState.isForStartupCache, Wallpaper: false },
   6668      };
   6669    case actionTypes.SHOW_PERSONALIZE:
   6670      return Object.assign({}, prevState, {
   6671        customizeMenuVisible: true,
   6672      });
   6673    case actionTypes.HIDE_PERSONALIZE:
   6674      return Object.assign({}, prevState, {
   6675        customizeMenuVisible: false,
   6676      });
   6677    default:
   6678      return prevState;
   6679  }
   6680 }
   6681 
   6682 function TopSites(prevState = INITIAL_STATE.TopSites, action) {
   6683  let hasMatch;
   6684  let newRows;
   6685  switch (action.type) {
   6686    case actionTypes.TOP_SITES_UPDATED:
   6687      if (!action.data || !action.data.links) {
   6688        return prevState;
   6689      }
   6690      return Object.assign(
   6691        {},
   6692        prevState,
   6693        { initialized: true, rows: action.data.links },
   6694        action.data.pref ? { pref: action.data.pref } : {}
   6695      );
   6696    case actionTypes.TOP_SITES_PREFS_UPDATED:
   6697      return Object.assign({}, prevState, { pref: action.data.pref });
   6698    case actionTypes.TOP_SITES_EDIT:
   6699      return Object.assign({}, prevState, {
   6700        editForm: {
   6701          index: action.data.index,
   6702          previewResponse: null,
   6703        },
   6704      });
   6705    case actionTypes.TOP_SITES_CANCEL_EDIT:
   6706      return Object.assign({}, prevState, { editForm: null });
   6707    case actionTypes.TOP_SITES_OPEN_SEARCH_SHORTCUTS_MODAL:
   6708      return Object.assign({}, prevState, { showSearchShortcutsForm: true });
   6709    case actionTypes.TOP_SITES_CLOSE_SEARCH_SHORTCUTS_MODAL:
   6710      return Object.assign({}, prevState, { showSearchShortcutsForm: false });
   6711    case actionTypes.PREVIEW_RESPONSE:
   6712      if (
   6713        !prevState.editForm ||
   6714        action.data.url !== prevState.editForm.previewUrl
   6715      ) {
   6716        return prevState;
   6717      }
   6718      return Object.assign({}, prevState, {
   6719        editForm: {
   6720          index: prevState.editForm.index,
   6721          previewResponse: action.data.preview,
   6722          previewUrl: action.data.url,
   6723        },
   6724      });
   6725    case actionTypes.PREVIEW_REQUEST:
   6726      if (!prevState.editForm) {
   6727        return prevState;
   6728      }
   6729      return Object.assign({}, prevState, {
   6730        editForm: {
   6731          index: prevState.editForm.index,
   6732          previewResponse: null,
   6733          previewUrl: action.data.url,
   6734        },
   6735      });
   6736    case actionTypes.PREVIEW_REQUEST_CANCEL:
   6737      if (!prevState.editForm) {
   6738        return prevState;
   6739      }
   6740      return Object.assign({}, prevState, {
   6741        editForm: {
   6742          index: prevState.editForm.index,
   6743          previewResponse: null,
   6744        },
   6745      });
   6746    case actionTypes.SCREENSHOT_UPDATED:
   6747      newRows = prevState.rows.map(row => {
   6748        if (row && row.url === action.data.url) {
   6749          hasMatch = true;
   6750          return Object.assign({}, row, { screenshot: action.data.screenshot });
   6751        }
   6752        return row;
   6753      });
   6754      return hasMatch
   6755        ? Object.assign({}, prevState, { rows: newRows })
   6756        : prevState;
   6757    case actionTypes.PLACES_BOOKMARK_ADDED:
   6758      if (!action.data) {
   6759        return prevState;
   6760      }
   6761      newRows = prevState.rows.map(site => {
   6762        if (site && site.url === action.data.url) {
   6763          const { bookmarkGuid, bookmarkTitle, dateAdded } = action.data;
   6764          return Object.assign({}, site, {
   6765            bookmarkGuid,
   6766            bookmarkTitle,
   6767            bookmarkDateCreated: dateAdded,
   6768          });
   6769        }
   6770        return site;
   6771      });
   6772      return Object.assign({}, prevState, { rows: newRows });
   6773    case actionTypes.PLACES_BOOKMARKS_REMOVED:
   6774      if (!action.data) {
   6775        return prevState;
   6776      }
   6777      newRows = prevState.rows.map(site => {
   6778        if (site && action.data.urls.includes(site.url)) {
   6779          const newSite = Object.assign({}, site);
   6780          delete newSite.bookmarkGuid;
   6781          delete newSite.bookmarkTitle;
   6782          delete newSite.bookmarkDateCreated;
   6783          return newSite;
   6784        }
   6785        return site;
   6786      });
   6787      return Object.assign({}, prevState, { rows: newRows });
   6788    case actionTypes.PLACES_LINKS_DELETED:
   6789      if (!action.data) {
   6790        return prevState;
   6791      }
   6792      newRows = prevState.rows.filter(
   6793        site => !action.data.urls.includes(site.url)
   6794      );
   6795      return Object.assign({}, prevState, { rows: newRows });
   6796    case actionTypes.UPDATE_SEARCH_SHORTCUTS:
   6797      return { ...prevState, searchShortcuts: action.data.searchShortcuts };
   6798    case actionTypes.SOV_UPDATED: {
   6799      const sov = {
   6800        ready: action.data.ready,
   6801        positions: action.data.positions,
   6802      };
   6803      return { ...prevState, sov };
   6804    }
   6805    default:
   6806      return prevState;
   6807  }
   6808 }
   6809 
   6810 function Dialog(prevState = INITIAL_STATE.Dialog, action) {
   6811  switch (action.type) {
   6812    case actionTypes.DIALOG_OPEN:
   6813      return Object.assign({}, prevState, { visible: true, data: action.data });
   6814    case actionTypes.DIALOG_CANCEL:
   6815      return Object.assign({}, prevState, { visible: false });
   6816    case actionTypes.DIALOG_CLOSE:
   6817      // Reset and hide the confirmation dialog once the action is complete.
   6818      return Object.assign({}, INITIAL_STATE.Dialog);
   6819    default:
   6820      return prevState;
   6821  }
   6822 }
   6823 
   6824 function Prefs(prevState = INITIAL_STATE.Prefs, action) {
   6825  let newValues;
   6826  switch (action.type) {
   6827    case actionTypes.PREFS_INITIAL_VALUES:
   6828      return Object.assign({}, prevState, {
   6829        initialized: true,
   6830        values: action.data,
   6831      });
   6832    case actionTypes.PREF_CHANGED:
   6833      newValues = Object.assign({}, prevState.values);
   6834      newValues[action.data.name] = action.data.value;
   6835      return Object.assign({}, prevState, { values: newValues });
   6836    default:
   6837      return prevState;
   6838  }
   6839 }
   6840 
   6841 function Sections(prevState = INITIAL_STATE.Sections, action) {
   6842  let hasMatch;
   6843  let newState;
   6844  switch (action.type) {
   6845    case actionTypes.SECTION_DEREGISTER:
   6846      return prevState.filter(section => section.id !== action.data);
   6847    case actionTypes.SECTION_REGISTER:
   6848      // If section exists in prevState, update it
   6849      newState = prevState.map(section => {
   6850        if (section && section.id === action.data.id) {
   6851          hasMatch = true;
   6852          return Object.assign({}, section, action.data);
   6853        }
   6854        return section;
   6855      });
   6856      // Otherwise, append it
   6857      if (!hasMatch) {
   6858        const initialized = !!(action.data.rows && !!action.data.rows.length);
   6859        const section = Object.assign(
   6860          { title: "", rows: [], enabled: false },
   6861          action.data,
   6862          { initialized }
   6863        );
   6864        newState.push(section);
   6865      }
   6866      return newState;
   6867    case actionTypes.SECTION_UPDATE:
   6868      newState = prevState.map(section => {
   6869        if (section && section.id === action.data.id) {
   6870          // If the action is updating rows, we should consider initialized to be true.
   6871          // This can be overridden if initialized is defined in the action.data
   6872          const initialized = action.data.rows ? { initialized: true } : {};
   6873 
   6874          // Make sure pinned cards stay at their current position when rows are updated.
   6875          // Disabling a section (SECTION_UPDATE with empty rows) does not retain pinned cards.
   6876          if (
   6877            action.data.rows &&
   6878            !!action.data.rows.length &&
   6879            section.rows.find(card => card.pinned)
   6880          ) {
   6881            const rows = Array.from(action.data.rows);
   6882            section.rows.forEach((card, index) => {
   6883              if (card.pinned) {
   6884                // Only add it if it's not already there.
   6885                if (rows[index].guid !== card.guid) {
   6886                  rows.splice(index, 0, card);
   6887                }
   6888              }
   6889            });
   6890            return Object.assign(
   6891              {},
   6892              section,
   6893              initialized,
   6894              Object.assign({}, action.data, { rows })
   6895            );
   6896          }
   6897 
   6898          return Object.assign({}, section, initialized, action.data);
   6899        }
   6900        return section;
   6901      });
   6902 
   6903      if (!action.data.dedupeConfigurations) {
   6904        return newState;
   6905      }
   6906 
   6907      action.data.dedupeConfigurations.forEach(dedupeConf => {
   6908        newState = newState.map(section => {
   6909          if (section.id === dedupeConf.id) {
   6910            const dedupedRows = dedupeConf.dedupeFrom.reduce(
   6911              (rows, dedupeSectionId) => {
   6912                const dedupeSection = newState.find(
   6913                  s => s.id === dedupeSectionId
   6914                );
   6915                const [, newRows] = dedupe.group(dedupeSection.rows, rows);
   6916                return newRows;
   6917              },
   6918              section.rows
   6919            );
   6920 
   6921            return Object.assign({}, section, { rows: dedupedRows });
   6922          }
   6923 
   6924          return section;
   6925        });
   6926      });
   6927 
   6928      return newState;
   6929    case actionTypes.SECTION_UPDATE_CARD:
   6930      return prevState.map(section => {
   6931        if (section && section.id === action.data.id && section.rows) {
   6932          const newRows = section.rows.map(card => {
   6933            if (card.url === action.data.url) {
   6934              return Object.assign({}, card, action.data.options);
   6935            }
   6936            return card;
   6937          });
   6938          return Object.assign({}, section, { rows: newRows });
   6939        }
   6940        return section;
   6941      });
   6942    case actionTypes.PLACES_BOOKMARK_ADDED:
   6943      if (!action.data) {
   6944        return prevState;
   6945      }
   6946      return prevState.map(section =>
   6947        Object.assign({}, section, {
   6948          rows: section.rows.map(item => {
   6949            // find the item within the rows that is attempted to be bookmarked
   6950            if (item.url === action.data.url) {
   6951              const { bookmarkGuid, bookmarkTitle, dateAdded } = action.data;
   6952              return Object.assign({}, item, {
   6953                bookmarkGuid,
   6954                bookmarkTitle,
   6955                bookmarkDateCreated: dateAdded,
   6956                type: "bookmark",
   6957              });
   6958            }
   6959            return item;
   6960          }),
   6961        })
   6962      );
   6963    case actionTypes.PLACES_BOOKMARKS_REMOVED:
   6964      if (!action.data) {
   6965        return prevState;
   6966      }
   6967      return prevState.map(section =>
   6968        Object.assign({}, section, {
   6969          rows: section.rows.map(item => {
   6970            // find the bookmark within the rows that is attempted to be removed
   6971            if (action.data.urls.includes(item.url)) {
   6972              const newSite = Object.assign({}, item);
   6973              delete newSite.bookmarkGuid;
   6974              delete newSite.bookmarkTitle;
   6975              delete newSite.bookmarkDateCreated;
   6976              if (!newSite.type || newSite.type === "bookmark") {
   6977                newSite.type = "history";
   6978              }
   6979              return newSite;
   6980            }
   6981            return item;
   6982          }),
   6983        })
   6984      );
   6985    case actionTypes.PLACES_LINKS_DELETED:
   6986      if (!action.data) {
   6987        return prevState;
   6988      }
   6989      return prevState.map(section =>
   6990        Object.assign({}, section, {
   6991          rows: section.rows.filter(
   6992            site => !action.data.urls.includes(site.url)
   6993          ),
   6994        })
   6995      );
   6996    case actionTypes.PLACES_LINK_BLOCKED:
   6997      if (!action.data) {
   6998        return prevState;
   6999      }
   7000      return prevState.map(section =>
   7001        Object.assign({}, section, {
   7002          rows: section.rows.filter(site => site.url !== action.data.url),
   7003        })
   7004      );
   7005    default:
   7006      return prevState;
   7007  }
   7008 }
   7009 
   7010 function Messages(prevState = INITIAL_STATE.Messages, action) {
   7011  switch (action.type) {
   7012    case actionTypes.MESSAGE_SET:
   7013      if (prevState.messageData.messageType) {
   7014        return prevState;
   7015      }
   7016      return {
   7017        ...prevState,
   7018        messageData: action.data.message,
   7019        portID: action.data.portID || "",
   7020      };
   7021    case actionTypes.MESSAGE_TOGGLE_VISIBILITY:
   7022      return { ...prevState, isVisible: action.data };
   7023    default:
   7024      return prevState;
   7025  }
   7026 }
   7027 
   7028 function Pocket(prevState = INITIAL_STATE.Pocket, action) {
   7029  switch (action.type) {
   7030    case actionTypes.POCKET_WAITING_FOR_SPOC:
   7031      return { ...prevState, waitingForSpoc: action.data };
   7032    case actionTypes.POCKET_CTA:
   7033      return {
   7034        ...prevState,
   7035        pocketCta: {
   7036          ctaButton: action.data.cta_button,
   7037          ctaText: action.data.cta_text,
   7038          ctaUrl: action.data.cta_url,
   7039          useCta: action.data.use_cta,
   7040        },
   7041      };
   7042    default:
   7043      return prevState;
   7044  }
   7045 }
   7046 
   7047 function Reducers_sys_Personalization(prevState = INITIAL_STATE.Personalization, action) {
   7048  switch (action.type) {
   7049    case actionTypes.DISCOVERY_STREAM_PERSONALIZATION_LAST_UPDATED:
   7050      return {
   7051        ...prevState,
   7052        lastUpdated: action.data.lastUpdated,
   7053      };
   7054    case actionTypes.DISCOVERY_STREAM_PERSONALIZATION_INIT:
   7055      return {
   7056        ...prevState,
   7057        initialized: true,
   7058      };
   7059    case actionTypes.DISCOVERY_STREAM_PERSONALIZATION_RESET:
   7060      return { ...INITIAL_STATE.Personalization };
   7061    default:
   7062      return prevState;
   7063  }
   7064 }
   7065 
   7066 function InferredPersonalization(
   7067  prevState = INITIAL_STATE.InferredPersonalization,
   7068  action
   7069 ) {
   7070  switch (action.type) {
   7071    case actionTypes.INFERRED_PERSONALIZATION_UPDATE:
   7072      return {
   7073        ...prevState,
   7074        initialized: true,
   7075        inferredInterests: action.data.inferredInterests,
   7076        coarseInferredInterests: action.data.coarseInferredInterests,
   7077        coarsePrivateInferredInterests:
   7078          action.data.coarsePrivateInferredInterests,
   7079        lastUpdated: action.data.lastUpdated,
   7080      };
   7081    case actionTypes.INFERRED_PERSONALIZATION_RESET:
   7082      return { ...INITIAL_STATE.InferredPersonalization };
   7083    default:
   7084      return prevState;
   7085  }
   7086 }
   7087 
   7088 // eslint-disable-next-line complexity
   7089 function DiscoveryStream(prevState = INITIAL_STATE.DiscoveryStream, action) {
   7090  // Return if action data is empty, or spocs or feeds data is not loaded
   7091  const isNotReady = () =>
   7092    !action.data || !prevState.spocs.loaded || !prevState.feeds.loaded;
   7093 
   7094  const handlePlacements = handleSites => {
   7095    const { data, placements } = prevState.spocs;
   7096    const result = {};
   7097 
   7098    const forPlacement = placement => {
   7099      const placementSpocs = data[placement.name];
   7100 
   7101      if (
   7102        !placementSpocs ||
   7103        !placementSpocs.items ||
   7104        !placementSpocs.items.length
   7105      ) {
   7106        return;
   7107      }
   7108 
   7109      result[placement.name] = {
   7110        ...placementSpocs,
   7111        items: handleSites(placementSpocs.items),
   7112      };
   7113    };
   7114 
   7115    if (!placements || !placements.length) {
   7116      [{ name: "spocs" }].forEach(forPlacement);
   7117    } else {
   7118      placements.forEach(forPlacement);
   7119    }
   7120    return result;
   7121  };
   7122 
   7123  const nextState = handleSites => ({
   7124    ...prevState,
   7125    spocs: {
   7126      ...prevState.spocs,
   7127      data: handlePlacements(handleSites),
   7128    },
   7129    feeds: {
   7130      ...prevState.feeds,
   7131      data: Object.keys(prevState.feeds.data).reduce(
   7132        (accumulator, feed_url) => {
   7133          accumulator[feed_url] = {
   7134            data: {
   7135              ...prevState.feeds.data[feed_url].data,
   7136              recommendations: handleSites(
   7137                prevState.feeds.data[feed_url].data.recommendations
   7138              ),
   7139            },
   7140          };
   7141          return accumulator;
   7142        },
   7143        {}
   7144      ),
   7145    },
   7146  });
   7147 
   7148  switch (action.type) {
   7149    case actionTypes.DISCOVERY_STREAM_CONFIG_CHANGE:
   7150    // Fall through to a separate action is so it doesn't trigger a listener update on init
   7151    case actionTypes.DISCOVERY_STREAM_CONFIG_SETUP:
   7152      return { ...prevState, config: action.data || {} };
   7153    case actionTypes.DISCOVERY_STREAM_EXPERIMENT_DATA:
   7154      return { ...prevState, experimentData: action.data || {} };
   7155    case actionTypes.DISCOVERY_STREAM_LAYOUT_UPDATE:
   7156      return {
   7157        ...prevState,
   7158        layout: action.data.layout || [],
   7159      };
   7160    case actionTypes.DISCOVERY_STREAM_TOPICS_LOADING:
   7161      return {
   7162        ...prevState,
   7163        topicsLoading: action.data,
   7164      };
   7165    case actionTypes.DISCOVERY_STREAM_PREFS_SETUP:
   7166      return {
   7167        ...prevState,
   7168        hideDescriptions: action.data.hideDescriptions,
   7169        compactImages: action.data.compactImages,
   7170        imageGradient: action.data.imageGradient,
   7171        newSponsoredLabel: action.data.newSponsoredLabel,
   7172        titleLines: action.data.titleLines,
   7173        descLines: action.data.descLines,
   7174        readTime: action.data.readTime,
   7175      };
   7176    case actionTypes.SHOW_PRIVACY_INFO:
   7177      return {
   7178        ...prevState,
   7179      };
   7180    case actionTypes.DISCOVERY_STREAM_LAYOUT_RESET:
   7181      return { ...INITIAL_STATE.DiscoveryStream, config: prevState.config };
   7182    case actionTypes.DISCOVERY_STREAM_FEEDS_UPDATE:
   7183      return {
   7184        ...prevState,
   7185        feeds: {
   7186          ...prevState.feeds,
   7187          loaded: true,
   7188        },
   7189      };
   7190    case actionTypes.DISCOVERY_STREAM_FEED_UPDATE: {
   7191      const newData = {};
   7192      newData[action.data.url] = action.data.feed;
   7193      return {
   7194        ...prevState,
   7195        feeds: {
   7196          ...prevState.feeds,
   7197          data: {
   7198            ...prevState.feeds.data,
   7199            ...newData,
   7200          },
   7201        },
   7202      };
   7203    }
   7204    case actionTypes.DISCOVERY_STREAM_DEV_IMPRESSIONS:
   7205      return {
   7206        ...prevState,
   7207        impressions: {
   7208          ...prevState.impressions,
   7209          feed: action.data,
   7210        },
   7211      };
   7212    case actionTypes.DISCOVERY_STREAM_DEV_BLOCKS:
   7213      return {
   7214        ...prevState,
   7215        blocks: action.data,
   7216      };
   7217    case actionTypes.DISCOVERY_STREAM_SPOCS_CAPS:
   7218      return {
   7219        ...prevState,
   7220        spocs: {
   7221          ...prevState.spocs,
   7222          frequency_caps: [...prevState.spocs.frequency_caps, ...action.data],
   7223        },
   7224      };
   7225    case actionTypes.DISCOVERY_STREAM_SPOCS_ENDPOINT:
   7226      return {
   7227        ...prevState,
   7228        spocs: {
   7229          ...INITIAL_STATE.DiscoveryStream.spocs,
   7230          spocs_endpoint:
   7231            action.data.url ||
   7232            INITIAL_STATE.DiscoveryStream.spocs.spocs_endpoint,
   7233        },
   7234      };
   7235    case actionTypes.DISCOVERY_STREAM_SPOCS_PLACEMENTS:
   7236      return {
   7237        ...prevState,
   7238        spocs: {
   7239          ...prevState.spocs,
   7240          placements:
   7241            action.data.placements ||
   7242            INITIAL_STATE.DiscoveryStream.spocs.placements,
   7243        },
   7244      };
   7245    case actionTypes.DISCOVERY_STREAM_SPOCS_UPDATE:
   7246      if (action.data) {
   7247        // If spocs have been loaded on this tab, we can ignore future updates.
   7248        // This should never be true on the main store, only content pages.
   7249        // We check agasint onDemand just to be safe. It generally shouldn't be needed.
   7250        if (prevState.spocs?.onDemand?.loaded) {
   7251          return prevState;
   7252        }
   7253        return {
   7254          ...prevState,
   7255          spocs: {
   7256            ...prevState.spocs,
   7257            lastUpdated: action.data.lastUpdated,
   7258            data: action.data.spocs,
   7259            cacheUpdateTime: action.data.spocsCacheUpdateTime,
   7260            onDemand: {
   7261              enabled: action.data.spocsOnDemand,
   7262              loaded: false,
   7263            },
   7264            loaded: true,
   7265          },
   7266        };
   7267      }
   7268      return prevState;
   7269    case actionTypes.DISCOVERY_STREAM_SPOCS_ONDEMAND_LOAD:
   7270      return {
   7271        ...prevState,
   7272        spocs: {
   7273          ...prevState.spocs,
   7274          onDemand: {
   7275            ...prevState.spocs.onDemand,
   7276            loaded: true,
   7277          },
   7278        },
   7279      };
   7280    case actionTypes.DISCOVERY_STREAM_SPOCS_ONDEMAND_RESET:
   7281      if (action.data) {
   7282        return {
   7283          ...prevState,
   7284          spocs: {
   7285            ...prevState.spocs,
   7286            cacheUpdateTime: action.data.spocsCacheUpdateTime,
   7287            onDemand: {
   7288              ...prevState.spocs.onDemand,
   7289              enabled: action.data.spocsOnDemand,
   7290            },
   7291          },
   7292        };
   7293      }
   7294      return prevState;
   7295    case actionTypes.DISCOVERY_STREAM_SPOC_BLOCKED:
   7296      return {
   7297        ...prevState,
   7298        spocs: {
   7299          ...prevState.spocs,
   7300          blocked: [...prevState.spocs.blocked, action.data.url],
   7301        },
   7302      };
   7303    case actionTypes.DISCOVERY_STREAM_LINK_BLOCKED:
   7304      return isNotReady()
   7305        ? prevState
   7306        : nextState(items =>
   7307            items.filter(item => item.url !== action.data.url)
   7308          );
   7309 
   7310    case actionTypes.PLACES_BOOKMARK_ADDED: {
   7311      const updateBookmarkInfo = item => {
   7312        if (item.url === action.data.url) {
   7313          const { bookmarkGuid, bookmarkTitle, dateAdded } = action.data;
   7314          return Object.assign({}, item, {
   7315            bookmarkGuid,
   7316            bookmarkTitle,
   7317            bookmarkDateCreated: dateAdded,
   7318            context_type: "bookmark",
   7319          });
   7320        }
   7321        return item;
   7322      };
   7323      return isNotReady()
   7324        ? prevState
   7325        : nextState(items => items.map(updateBookmarkInfo));
   7326    }
   7327    case actionTypes.PLACES_BOOKMARKS_REMOVED: {
   7328      const removeBookmarkInfo = item => {
   7329        if (action.data.urls.includes(item.url)) {
   7330          const newSite = Object.assign({}, item);
   7331          delete newSite.bookmarkGuid;
   7332          delete newSite.bookmarkTitle;
   7333          delete newSite.bookmarkDateCreated;
   7334          if (!newSite.context_type || newSite.context_type === "bookmark") {
   7335            newSite.context_type = "removedBookmark";
   7336          }
   7337          return newSite;
   7338        }
   7339        return item;
   7340      };
   7341      return isNotReady()
   7342        ? prevState
   7343        : nextState(items => items.map(removeBookmarkInfo));
   7344    }
   7345    case actionTypes.TOPIC_SELECTION_SPOTLIGHT_OPEN:
   7346      return {
   7347        ...prevState,
   7348        showTopicSelection: true,
   7349      };
   7350    case actionTypes.TOPIC_SELECTION_SPOTLIGHT_CLOSE:
   7351      return {
   7352        ...prevState,
   7353        showTopicSelection: false,
   7354      };
   7355    case actionTypes.SECTION_BLOCKED:
   7356      return {
   7357        ...prevState,
   7358        showBlockSectionConfirmation: true,
   7359        sectionPersonalization: action.data,
   7360      };
   7361    case actionTypes.REPORT_AD_OPEN:
   7362      return {
   7363        ...prevState,
   7364        report: {
   7365          ...prevState.report,
   7366          card_type: action.data?.card_type,
   7367          position: action.data?.position,
   7368          placement_id: action.data?.placement_id,
   7369          reporting_url: action.data?.reporting_url,
   7370          url: action.data?.url,
   7371          visible: true,
   7372        },
   7373      };
   7374    case actionTypes.REPORT_CONTENT_OPEN:
   7375      return {
   7376        ...prevState,
   7377        report: {
   7378          ...prevState.report,
   7379          card_type: action.data?.card_type,
   7380          corpus_item_id: action.data?.corpus_item_id,
   7381          scheduled_corpus_item_id: action.data?.scheduled_corpus_item_id,
   7382          section_position: action.data?.section_position,
   7383          section: action.data?.section,
   7384          title: action.data?.title,
   7385          topic: action.data?.topic,
   7386          url: action.data?.url,
   7387          visible: true,
   7388        },
   7389      };
   7390    case actionTypes.REPORT_CLOSE:
   7391    case actionTypes.REPORT_AD_SUBMIT:
   7392    case actionTypes.REPORT_CONTENT_SUBMIT:
   7393      return {
   7394        ...prevState,
   7395        report: {
   7396          ...prevState.report,
   7397          visible: false,
   7398        },
   7399      };
   7400    case actionTypes.SECTION_PERSONALIZATION_UPDATE:
   7401      return { ...prevState, sectionPersonalization: action.data };
   7402    default:
   7403      return prevState;
   7404  }
   7405 }
   7406 
   7407 function Search(prevState = INITIAL_STATE.Search, action) {
   7408  switch (action.type) {
   7409    case actionTypes.DISABLE_SEARCH:
   7410      return Object.assign({ ...prevState, disable: true });
   7411    case actionTypes.FAKE_FOCUS_SEARCH:
   7412      return Object.assign({ ...prevState, fakeFocus: true });
   7413    case actionTypes.SHOW_SEARCH:
   7414      return Object.assign({ ...prevState, disable: false, fakeFocus: false });
   7415    default:
   7416      return prevState;
   7417  }
   7418 }
   7419 
   7420 function Wallpapers(prevState = INITIAL_STATE.Wallpapers, action) {
   7421  switch (action.type) {
   7422    case actionTypes.WALLPAPERS_SET:
   7423      return {
   7424        ...prevState,
   7425        wallpaperList: action.data,
   7426      };
   7427    case actionTypes.WALLPAPERS_FEATURE_HIGHLIGHT_COUNTER_INCREMENT:
   7428      return {
   7429        ...prevState,
   7430        highlightSeenCounter: action.data,
   7431      };
   7432    case actionTypes.WALLPAPERS_CATEGORY_SET:
   7433      return { ...prevState, categories: action.data };
   7434    case actionTypes.WALLPAPERS_CUSTOM_SET:
   7435      return { ...prevState, uploadedWallpaper: action.data };
   7436    default:
   7437      return prevState;
   7438  }
   7439 }
   7440 
   7441 function Notifications(prevState = INITIAL_STATE.Notifications, action) {
   7442  switch (action.type) {
   7443    case actionTypes.SHOW_TOAST_MESSAGE:
   7444      return {
   7445        ...prevState,
   7446        showNotifications: action.data.showNotifications,
   7447        toastCounter: prevState.toastCounter + 1,
   7448        toastId: action.data.toastId,
   7449        toastQueue: [action.data.toastId],
   7450      };
   7451    case actionTypes.HIDE_TOAST_MESSAGE: {
   7452      const { showNotifications, toastId: hiddenToastId } = action.data;
   7453      const queuedToasts = [...prevState.toastQueue].filter(
   7454        toastId => toastId !== hiddenToastId
   7455      );
   7456      return {
   7457        ...prevState,
   7458        toastCounter: queuedToasts.length,
   7459        toastQueue: queuedToasts,
   7460        toastId: "",
   7461        showNotifications,
   7462      };
   7463    }
   7464    default:
   7465      return prevState;
   7466  }
   7467 }
   7468 
   7469 function Weather(prevState = INITIAL_STATE.Weather, action) {
   7470  switch (action.type) {
   7471    case actionTypes.WEATHER_UPDATE:
   7472      return {
   7473        ...prevState,
   7474        suggestions: action.data.suggestions,
   7475        lastUpdated: action.data.date,
   7476        locationData: action.data.locationData || prevState.locationData,
   7477        initialized: true,
   7478      };
   7479    case actionTypes.WEATHER_SEARCH_ACTIVE:
   7480      return { ...prevState, searchActive: action.data };
   7481    case actionTypes.WEATHER_LOCATION_SEARCH_UPDATE:
   7482      return { ...prevState, locationSearchString: action.data };
   7483    case actionTypes.WEATHER_LOCATION_SUGGESTIONS_UPDATE:
   7484      return { ...prevState, suggestedLocations: action.data };
   7485    case actionTypes.WEATHER_LOCATION_DATA_UPDATE:
   7486      return { ...prevState, locationData: action.data };
   7487    default:
   7488      return prevState;
   7489  }
   7490 }
   7491 
   7492 function Ads(prevState = INITIAL_STATE.Ads, action) {
   7493  switch (action.type) {
   7494    case actionTypes.ADS_INIT:
   7495      return {
   7496        ...prevState,
   7497        initialized: true,
   7498      };
   7499    case actionTypes.ADS_UPDATE_TILES:
   7500      return {
   7501        ...prevState,
   7502        tiles: action.data.tiles,
   7503      };
   7504    case actionTypes.ADS_UPDATE_SPOCS:
   7505      return {
   7506        ...prevState,
   7507        spocs: action.data.spocs,
   7508        spocPlacements: action.data.spocPlacements,
   7509      };
   7510    case actionTypes.ADS_RESET:
   7511      return { ...INITIAL_STATE.Ads };
   7512    default:
   7513      return prevState;
   7514  }
   7515 }
   7516 
   7517 function TimerWidget(prevState = INITIAL_STATE.TimerWidget, action) {
   7518  // fallback to current timerType in state if not provided in action
   7519  const timerType = action.data?.timerType || prevState.timerType;
   7520  switch (action.type) {
   7521    case actionTypes.WIDGETS_TIMER_SET:
   7522      return {
   7523        ...prevState,
   7524        ...action.data,
   7525      };
   7526    case actionTypes.WIDGETS_TIMER_SET_TYPE:
   7527      return {
   7528        ...prevState,
   7529        timerType: action.data.timerType,
   7530      };
   7531    case actionTypes.WIDGETS_TIMER_SET_DURATION:
   7532      return {
   7533        ...prevState,
   7534        [timerType]: {
   7535          // setting a dynamic key assignment to let us dynamically update timer type's state based on what is set
   7536          duration: action.data.duration,
   7537          initialDuration: action.data.duration,
   7538          startTime: null,
   7539          isRunning: false,
   7540        },
   7541      };
   7542    case actionTypes.WIDGETS_TIMER_PLAY:
   7543      return {
   7544        ...prevState,
   7545        [timerType]: {
   7546          ...prevState[timerType],
   7547          startTime: Math.floor(Date.now() / 1000), // reflected in seconds
   7548          isRunning: true,
   7549        },
   7550      };
   7551    case actionTypes.WIDGETS_TIMER_PAUSE:
   7552      if (prevState[timerType]?.isRunning) {
   7553        return {
   7554          ...prevState,
   7555          [timerType]: {
   7556            ...prevState[timerType],
   7557            duration: action.data.duration,
   7558            // setting startTime to null on pause because we need to check the exact time the user presses play,
   7559            // whether it's when the user starts or resumes the timer. This helps get accurate results
   7560            startTime: null,
   7561            isRunning: false,
   7562          },
   7563        };
   7564      }
   7565      return prevState;
   7566    case actionTypes.WIDGETS_TIMER_RESET:
   7567      return {
   7568        ...prevState,
   7569        [timerType]: {
   7570          ...prevState[timerType],
   7571          duration: action.data.duration,
   7572          initialDuration: action.data.duration,
   7573          startTime: null,
   7574          isRunning: false,
   7575        },
   7576      };
   7577    case actionTypes.WIDGETS_TIMER_END:
   7578      return {
   7579        ...prevState,
   7580        [timerType]: {
   7581          ...prevState[timerType],
   7582          duration: action.data.duration,
   7583          initialDuration: action.data.duration,
   7584          startTime: null,
   7585          isRunning: false,
   7586        },
   7587      };
   7588    default:
   7589      return prevState;
   7590  }
   7591 }
   7592 
   7593 function ListsWidget(prevState = INITIAL_STATE.ListsWidget, action) {
   7594  switch (action.type) {
   7595    case actionTypes.WIDGETS_LISTS_SET:
   7596      return { ...prevState, lists: action.data };
   7597    case actionTypes.WIDGETS_LISTS_SET_SELECTED:
   7598      return { ...prevState, selected: action.data };
   7599    default:
   7600      return prevState;
   7601  }
   7602 }
   7603 
   7604 function ExternalComponents(
   7605  prevState = INITIAL_STATE.ExternalComponents,
   7606  action
   7607 ) {
   7608  switch (action.type) {
   7609    case actionTypes.REFRESH_EXTERNAL_COMPONENTS:
   7610      return { ...prevState, components: action.data };
   7611    default:
   7612      return prevState;
   7613  }
   7614 }
   7615 
   7616 const reducers = {
   7617  TopSites,
   7618  App,
   7619  Ads,
   7620  Prefs,
   7621  Dialog,
   7622  Sections,
   7623  Messages,
   7624  Notifications,
   7625  Pocket,
   7626  Personalization: Reducers_sys_Personalization,
   7627  InferredPersonalization,
   7628  DiscoveryStream,
   7629  Search,
   7630  TimerWidget,
   7631  ListsWidget,
   7632  Wallpapers,
   7633  Weather,
   7634  ExternalComponents,
   7635 };
   7636 
   7637 ;// CONCATENATED MODULE: ./content-src/components/TopSites/TopSiteFormInput.jsx
   7638 /* This Source Code Form is subject to the terms of the Mozilla Public
   7639 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   7640 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   7641 
   7642 
   7643 class TopSiteFormInput extends (external_React_default()).PureComponent {
   7644  constructor(props) {
   7645    super(props);
   7646    this.state = {
   7647      validationError: this.props.validationError
   7648    };
   7649    this.onChange = this.onChange.bind(this);
   7650    this.onMount = this.onMount.bind(this);
   7651    this.onClearIconPress = this.onClearIconPress.bind(this);
   7652  }
   7653  componentWillReceiveProps(nextProps) {
   7654    if (nextProps.shouldFocus && !this.props.shouldFocus) {
   7655      this.input.focus();
   7656    }
   7657    if (nextProps.validationError && !this.props.validationError) {
   7658      this.setState({
   7659        validationError: true
   7660      });
   7661    }
   7662    // If the component is in an error state but the value was cleared by the parent
   7663    if (this.state.validationError && !nextProps.value) {
   7664      this.setState({
   7665        validationError: false
   7666      });
   7667    }
   7668  }
   7669  onClearIconPress(event) {
   7670    // If there is input in the URL or custom image URL fields,
   7671    // and we hit 'enter' while tabbed over the clear icon,
   7672    // we should execute the function to clear the field.
   7673    if (event.key === "Enter") {
   7674      this.props.onClear();
   7675    }
   7676  }
   7677  onChange(ev) {
   7678    if (this.state.validationError) {
   7679      this.setState({
   7680        validationError: false
   7681      });
   7682    }
   7683    this.props.onChange(ev);
   7684  }
   7685  onMount(input) {
   7686    this.input = input;
   7687  }
   7688  renderLoadingOrCloseButton() {
   7689    const showClearButton = this.props.value && this.props.onClear;
   7690    if (this.props.loading) {
   7691      return /*#__PURE__*/external_React_default().createElement("div", {
   7692        className: "loading-container"
   7693      }, /*#__PURE__*/external_React_default().createElement("div", {
   7694        className: "loading-animation"
   7695      }));
   7696    } else if (showClearButton) {
   7697      return /*#__PURE__*/external_React_default().createElement("button", {
   7698        type: "button",
   7699        className: "icon icon-clear-input icon-button-style",
   7700        onClick: this.props.onClear,
   7701        onKeyPress: this.onClearIconPress
   7702      });
   7703    }
   7704    return null;
   7705  }
   7706  render() {
   7707    const {
   7708      typeUrl
   7709    } = this.props;
   7710    const {
   7711      validationError
   7712    } = this.state;
   7713    return /*#__PURE__*/external_React_default().createElement("label", null, /*#__PURE__*/external_React_default().createElement("span", {
   7714      "data-l10n-id": this.props.titleId
   7715    }), /*#__PURE__*/external_React_default().createElement("div", {
   7716      className: `field ${typeUrl ? "url" : ""}${validationError ? " invalid" : ""}`
   7717    }, /*#__PURE__*/external_React_default().createElement("input", {
   7718      type: "text",
   7719      value: this.props.value,
   7720      ref: this.onMount,
   7721      onChange: this.onChange,
   7722      "data-l10n-id": this.props.placeholderId
   7723      // Set focus on error if the url field is valid or when the input is first rendered and is empty
   7724      // eslint-disable-next-line jsx-a11y/no-autofocus
   7725      ,
   7726      autoFocus: this.props.autoFocusOnOpen,
   7727      disabled: this.props.loading
   7728    }), this.renderLoadingOrCloseButton(), validationError && /*#__PURE__*/external_React_default().createElement("aside", {
   7729      className: "error-tooltip",
   7730      "data-l10n-id": this.props.errorMessageId
   7731    })));
   7732  }
   7733 }
   7734 TopSiteFormInput.defaultProps = {
   7735  showClearButton: false,
   7736  value: "",
   7737  validationError: false
   7738 };
   7739 ;// CONCATENATED MODULE: ./content-src/components/TopSites/TopSiteImpressionWrapper.jsx
   7740 /* This Source Code Form is subject to the terms of the Mozilla Public
   7741 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   7742 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   7743 
   7744 
   7745 
   7746 const TopSiteImpressionWrapper_VISIBLE = "visible";
   7747 const TopSiteImpressionWrapper_VISIBILITY_CHANGE_EVENT = "visibilitychange";
   7748 
   7749 // Per analytical requirement, we set the minimal intersection ratio to
   7750 // 0.5, and an impression is identified when the wrapped item has at least
   7751 // 50% visibility.
   7752 //
   7753 // This constant is exported for unit test
   7754 const TopSiteImpressionWrapper_INTERSECTION_RATIO = 0.5;
   7755 
   7756 /**
   7757 * Impression wrapper for a TopSite tile.
   7758 *
   7759 * It makses use of the Intersection Observer API to detect the visibility,
   7760 * and relies on page visibility to ensure the impression is reported
   7761 * only when the component is visible on the page.
   7762 */
   7763 class TopSiteImpressionWrapper extends (external_React_default()).PureComponent {
   7764  _dispatchImpressionStats() {
   7765    const {
   7766      actionType,
   7767      tile
   7768    } = this.props;
   7769    if (!actionType) {
   7770      return;
   7771    }
   7772    this.props.dispatch(actionCreators.OnlyToMain({
   7773      type: actionType,
   7774      data: {
   7775        type: "impression",
   7776        ...tile
   7777      }
   7778    }));
   7779  }
   7780  setImpressionObserverOrAddListener() {
   7781    const {
   7782      props
   7783    } = this;
   7784    if (!props.dispatch) {
   7785      return;
   7786    }
   7787    if (props.document.visibilityState === TopSiteImpressionWrapper_VISIBLE) {
   7788      this.setImpressionObserver();
   7789    } else {
   7790      // We should only ever send the latest impression stats ping, so remove any
   7791      // older listeners.
   7792      if (this._onVisibilityChange) {
   7793        props.document.removeEventListener(TopSiteImpressionWrapper_VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
   7794      }
   7795      this._onVisibilityChange = () => {
   7796        if (props.document.visibilityState === TopSiteImpressionWrapper_VISIBLE) {
   7797          this.setImpressionObserver();
   7798          props.document.removeEventListener(TopSiteImpressionWrapper_VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
   7799        }
   7800      };
   7801      props.document.addEventListener(TopSiteImpressionWrapper_VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
   7802    }
   7803  }
   7804 
   7805  /**
   7806   * Set an impression observer for the wrapped component. It makes use of
   7807   * the Intersection Observer API to detect if the wrapped component is
   7808   * visible with a desired ratio, and only sends impression if that's the case.
   7809   *
   7810   * See more details about Intersection Observer API at:
   7811   * https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
   7812   */
   7813  setImpressionObserver() {
   7814    const {
   7815      props
   7816    } = this;
   7817    if (!props.tile) {
   7818      return;
   7819    }
   7820    this._handleIntersect = entries => {
   7821      if (entries.some(entry => entry.isIntersecting && entry.intersectionRatio >= TopSiteImpressionWrapper_INTERSECTION_RATIO)) {
   7822        this._dispatchImpressionStats();
   7823        this.impressionObserver.unobserve(this.refs.topsite_impression_wrapper);
   7824      }
   7825    };
   7826    const options = {
   7827      threshold: TopSiteImpressionWrapper_INTERSECTION_RATIO
   7828    };
   7829    this.impressionObserver = new props.IntersectionObserver(this._handleIntersect, options);
   7830    this.impressionObserver.observe(this.refs.topsite_impression_wrapper);
   7831  }
   7832  componentDidMount() {
   7833    if (this.props.tile) {
   7834      this.setImpressionObserverOrAddListener();
   7835    }
   7836  }
   7837  componentWillUnmount() {
   7838    if (this._handleIntersect && this.impressionObserver) {
   7839      this.impressionObserver.unobserve(this.refs.topsite_impression_wrapper);
   7840    }
   7841    if (this._onVisibilityChange) {
   7842      this.props.document.removeEventListener(TopSiteImpressionWrapper_VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
   7843    }
   7844  }
   7845  render() {
   7846    return /*#__PURE__*/external_React_default().createElement("div", {
   7847      ref: "topsite_impression_wrapper",
   7848      className: "topsite-impression-observer"
   7849    }, this.props.children);
   7850  }
   7851 }
   7852 TopSiteImpressionWrapper.defaultProps = {
   7853  IntersectionObserver: globalThis.IntersectionObserver,
   7854  document: globalThis.document,
   7855  actionType: null,
   7856  tile: null
   7857 };
   7858 ;// CONCATENATED MODULE: ./content-src/components/MessageWrapper/MessageWrapper.jsx
   7859 /* This Source Code Form is subject to the terms of the Mozilla Public
   7860 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   7861 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   7862 
   7863 
   7864 
   7865 
   7866 
   7867 
   7868 // Note: MessageWrapper emits events via submitGleanPingForPing() in the OMC messaging-system.
   7869 // If a feature is triggered outside of this flow (e.g., the Mobile Download QR Promo),
   7870 // it should emit New Tab-specific Glean events independently.
   7871 
   7872 function MessageWrapper({
   7873  children,
   7874  dispatch,
   7875  hiddenOverride,
   7876  onDismiss
   7877 }) {
   7878  const message = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.Messages);
   7879  const [isIntersecting, setIsIntersecting] = (0,external_React_namespaceObject.useState)(false);
   7880  const [tabIsVisible, setTabIsVisible] = (0,external_React_namespaceObject.useState)(() => typeof document !== "undefined" && document.visibilityState === "visible");
   7881  const [hasRun, setHasRun] = (0,external_React_namespaceObject.useState)();
   7882  const handleIntersection = (0,external_React_namespaceObject.useCallback)(() => {
   7883    setIsIntersecting(true);
   7884    // only send impression if messageId is defined and tab is visible
   7885    if (tabIsVisible && message.messageData.id && !hasRun) {
   7886      setHasRun(true);
   7887      dispatch(actionCreators.AlsoToMain({
   7888        type: actionTypes.MESSAGE_IMPRESSION,
   7889        data: message.messageData
   7890      }));
   7891    }
   7892  }, [dispatch, message, tabIsVisible, hasRun]);
   7893  (0,external_React_namespaceObject.useEffect)(() => {
   7894    // we dont want to dispatch this action unless the current tab is open and visible
   7895    if (message.isVisible && tabIsVisible) {
   7896      dispatch(actionCreators.AlsoToMain({
   7897        type: actionTypes.MESSAGE_NOTIFY_VISIBILITY,
   7898        data: true
   7899      }));
   7900    }
   7901  }, [message, dispatch, tabIsVisible]);
   7902  (0,external_React_namespaceObject.useEffect)(() => {
   7903    const handleVisibilityChange = () => {
   7904      setTabIsVisible(document.visibilityState === "visible");
   7905    };
   7906    document.addEventListener("visibilitychange", handleVisibilityChange);
   7907    return () => {
   7908      document.removeEventListener("visibilitychange", handleVisibilityChange);
   7909    };
   7910  }, []);
   7911  const ref = useIntersectionObserver(handleIntersection);
   7912  const handleClose = (0,external_React_namespaceObject.useCallback)(() => {
   7913    const action = {
   7914      type: actionTypes.MESSAGE_TOGGLE_VISIBILITY,
   7915      data: false //isVisible
   7916    };
   7917    if (message.portID) {
   7918      dispatch(actionCreators.OnlyToOneContent(action, message.portID));
   7919    } else {
   7920      dispatch(actionCreators.AlsoToMain(action));
   7921    }
   7922    dispatch(actionCreators.AlsoToMain({
   7923      type: actionTypes.MESSAGE_NOTIFY_VISIBILITY,
   7924      data: false
   7925    }));
   7926    onDismiss?.();
   7927  }, [dispatch, message, onDismiss]);
   7928  function handleDismiss() {
   7929    const {
   7930      id
   7931    } = message.messageData;
   7932    if (id) {
   7933      dispatch(actionCreators.OnlyToMain({
   7934        type: actionTypes.MESSAGE_DISMISS,
   7935        data: {
   7936          message: message.messageData
   7937        }
   7938      }));
   7939    }
   7940    handleClose();
   7941  }
   7942  function handleBlock() {
   7943    const {
   7944      id
   7945    } = message.messageData;
   7946    if (id) {
   7947      dispatch(actionCreators.OnlyToMain({
   7948        type: actionTypes.MESSAGE_BLOCK,
   7949        data: id
   7950      }));
   7951    }
   7952  }
   7953  function handleClick(elementId) {
   7954    const {
   7955      id
   7956    } = message.messageData;
   7957    if (id) {
   7958      dispatch(actionCreators.OnlyToMain({
   7959        type: actionTypes.MESSAGE_CLICK,
   7960        data: {
   7961          message: message.messageData,
   7962          source: elementId || ""
   7963        }
   7964      }));
   7965    }
   7966  }
   7967  if (!message || !hiddenOverride && !message.isVisible) {
   7968    return null;
   7969  }
   7970 
   7971  // only display the message if `isVisible` is true
   7972  return /*#__PURE__*/external_React_default().createElement("div", {
   7973    ref: el => {
   7974      ref.current = [el];
   7975    },
   7976    className: "message-wrapper"
   7977  }, /*#__PURE__*/external_React_default().cloneElement(children, {
   7978    isIntersecting,
   7979    handleDismiss,
   7980    handleClick,
   7981    handleBlock,
   7982    handleClose
   7983  }));
   7984 }
   7985 
   7986 ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/FeatureHighlight/FeatureHighlight.jsx
   7987 /* This Source Code Form is subject to the terms of the Mozilla Public
   7988 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   7989 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   7990 
   7991 
   7992 
   7993 function FeatureHighlight({
   7994  message,
   7995  icon,
   7996  toggle,
   7997  arrowPosition = "",
   7998  position = "top-left",
   7999  verticalPosition = "",
   8000  title,
   8001  ariaLabel,
   8002  feature = "FEATURE_HIGHLIGHT_DEFAULT",
   8003  dispatch = () => {},
   8004  windowObj = __webpack_require__.g,
   8005  openedOverride = false,
   8006  showButtonIcon = true,
   8007  dismissCallback = () => {},
   8008  outsideClickCallback = () => {},
   8009  modalClassName = ""
   8010 }) {
   8011  const [opened, setOpened] = (0,external_React_namespaceObject.useState)(openedOverride);
   8012  const ref = (0,external_React_namespaceObject.useRef)(null);
   8013  (0,external_React_namespaceObject.useEffect)(() => {
   8014    const handleOutsideClick = e => {
   8015      if (!ref?.current?.contains(e.target)) {
   8016        setOpened(false);
   8017        outsideClickCallback();
   8018      }
   8019    };
   8020    const handleKeyDown = e => {
   8021      if (e.key === "Escape") {
   8022        outsideClickCallback();
   8023      }
   8024    };
   8025    windowObj.document.addEventListener("click", handleOutsideClick);
   8026    windowObj.document.addEventListener("keydown", handleKeyDown);
   8027    return () => {
   8028      windowObj.document.removeEventListener("click", handleOutsideClick);
   8029      windowObj.document.removeEventListener("keydown", handleKeyDown);
   8030    };
   8031  }, [windowObj, outsideClickCallback]);
   8032  const onToggleClick = (0,external_React_namespaceObject.useCallback)(() => {
   8033    if (!opened) {
   8034      dispatch(actionCreators.DiscoveryStreamUserEvent({
   8035        event: "CLICK",
   8036        source: "FEATURE_HIGHLIGHT",
   8037        value: {
   8038          feature
   8039        }
   8040      }));
   8041    }
   8042    setOpened(!opened);
   8043  }, [dispatch, feature, opened]);
   8044  const onDismissClick = (0,external_React_namespaceObject.useCallback)(() => {
   8045    setOpened(false);
   8046    dismissCallback();
   8047  }, [dismissCallback]);
   8048  const hideButtonClass = showButtonIcon ? `` : `isHidden`;
   8049  const openedClassname = opened ? `opened` : `closed`;
   8050  return /*#__PURE__*/external_React_default().createElement("div", {
   8051    ref: ref,
   8052    className: `feature-highlight ${verticalPosition}`
   8053  }, /*#__PURE__*/external_React_default().createElement("button", {
   8054    title: title,
   8055    "aria-haspopup": "true",
   8056    "aria-label": ariaLabel,
   8057    className: `toggle-button ${hideButtonClass}`,
   8058    onClick: onToggleClick
   8059  }, toggle), /*#__PURE__*/external_React_default().createElement("div", {
   8060    className: `feature-highlight-modal ${position} ${arrowPosition} ${modalClassName} ${openedClassname}`
   8061  }, /*#__PURE__*/external_React_default().createElement("div", {
   8062    className: "message-icon"
   8063  }, icon), /*#__PURE__*/external_React_default().createElement("p", {
   8064    className: "content-wrapper"
   8065  }, message), /*#__PURE__*/external_React_default().createElement("moz-button", {
   8066    type: "icon ghost",
   8067    size: "small",
   8068    "data-l10n-id": "feature-highlight-dismiss-button",
   8069    iconsrc: "chrome://global/skin/icons/close.svg",
   8070    onClick: onDismissClick,
   8071    onKeyDown: onDismissClick
   8072  })));
   8073 }
   8074 ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/FeatureHighlight/ShortcutFeatureHighlight.jsx
   8075 /* This Source Code Form is subject to the terms of the Mozilla Public
   8076 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   8077 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   8078 
   8079 
   8080 
   8081 function ShortcutFeatureHighlight({
   8082  dispatch,
   8083  feature,
   8084  handleBlock,
   8085  handleDismiss,
   8086  messageData,
   8087  position
   8088 }) {
   8089  const onDismiss = (0,external_React_namespaceObject.useCallback)(() => {
   8090    handleDismiss();
   8091    handleBlock();
   8092  }, [handleDismiss, handleBlock]);
   8093  return /*#__PURE__*/external_React_default().createElement("div", {
   8094    className: `shortcut-feature-highlight ${messageData.content?.darkModeDismiss ? "is-inverted-dark-dismiss-button" : ""}`
   8095  }, /*#__PURE__*/external_React_default().createElement(FeatureHighlight, {
   8096    position: position,
   8097    feature: feature,
   8098    dispatch: dispatch,
   8099    message: /*#__PURE__*/external_React_default().createElement("div", {
   8100      className: "shortcut-feature-highlight-content"
   8101    }, /*#__PURE__*/external_React_default().createElement("picture", {
   8102      className: "follow-section-button-highlight-image"
   8103    }, /*#__PURE__*/external_React_default().createElement("source", {
   8104      srcSet: messageData.content?.darkModeImageURL || "chrome://newtab/content/data/content/assets/highlights/omc-newtab-shortcuts.svg",
   8105      media: "(prefers-color-scheme: dark)"
   8106    }), /*#__PURE__*/external_React_default().createElement("source", {
   8107      srcSet: messageData.content?.imageURL || "chrome://newtab/content/data/content/assets/highlights/omc-newtab-shortcuts.svg",
   8108      media: "(prefers-color-scheme: light)"
   8109    }), /*#__PURE__*/external_React_default().createElement("img", {
   8110      width: "320",
   8111      height: "195",
   8112      alt: ""
   8113    })), /*#__PURE__*/external_React_default().createElement("div", {
   8114      className: "shortcut-feature-highlight-copy"
   8115    }, messageData.content?.cardTitle ? /*#__PURE__*/external_React_default().createElement("p", {
   8116      className: "title"
   8117    }, messageData.content.cardTitle) : /*#__PURE__*/external_React_default().createElement("p", {
   8118      className: "title",
   8119      "data-l10n-id": "newtab-shortcuts-highlight-title"
   8120    }), messageData.content?.cardMessage ? /*#__PURE__*/external_React_default().createElement("p", {
   8121      className: "subtitle"
   8122    }, messageData.content.cardMessage) : /*#__PURE__*/external_React_default().createElement("p", {
   8123      className: "subtitle",
   8124      "data-l10n-id": "newtab-shortcuts-highlight-subtitle"
   8125    }))),
   8126    openedOverride: true,
   8127    showButtonIcon: false,
   8128    dismissCallback: onDismiss,
   8129    outsideClickCallback: handleDismiss
   8130  }));
   8131 }
   8132 ;// CONCATENATED MODULE: ./content-src/components/TopSites/TopSite.jsx
   8133 function TopSite_extends() { return TopSite_extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, TopSite_extends.apply(null, arguments); }
   8134 /* This Source Code Form is subject to the terms of the Mozilla Public
   8135 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   8136 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   8137 
   8138 
   8139 
   8140 
   8141 
   8142 
   8143 
   8144 
   8145 
   8146 
   8147 
   8148 
   8149 
   8150 const SPOC_TYPE = "SPOC";
   8151 const NEWTAB_SOURCE = "newtab";
   8152 
   8153 // For cases if we want to know if this is sponsored by either sponsored_position or type.
   8154 // We have two sources for sponsored topsites, and
   8155 // sponsored_position is set by one sponsored source, and type is set by another.
   8156 // This is not called in all cases, sometimes we want to know if it's one source
   8157 // or the other. This function is only applicable in cases where we only care if it's either.
   8158 function isSponsored(link) {
   8159  return link?.sponsored_position || link?.type === SPOC_TYPE;
   8160 }
   8161 class TopSiteLink extends (external_React_default()).PureComponent {
   8162  constructor(props) {
   8163    super(props);
   8164    this.state = {
   8165      screenshotImage: null
   8166    };
   8167    this.onDragEvent = this.onDragEvent.bind(this);
   8168    this.onKeyPress = this.onKeyPress.bind(this);
   8169    this.shouldShowOMCHighlight = this.shouldShowOMCHighlight.bind(this);
   8170  }
   8171 
   8172  /*
   8173   * Helper to determine whether the drop zone should allow a drop. We only allow
   8174   * dropping top sites for now. We don't allow dropping on sponsored top sites
   8175   * as their position is fixed.
   8176   */
   8177  _allowDrop(e) {
   8178    return (this.dragged || !isSponsored(this.props.link)) && e.dataTransfer.types.includes("text/topsite-index");
   8179  }
   8180  onDragEvent(event) {
   8181    switch (event.type) {
   8182      case "click":
   8183        // Stop any link clicks if we started any dragging
   8184        if (this.dragged) {
   8185          event.preventDefault();
   8186        }
   8187        break;
   8188      case "dragstart":
   8189        event.target.blur();
   8190        if (isSponsored(this.props.link)) {
   8191          event.preventDefault();
   8192          break;
   8193        }
   8194        this.dragged = true;
   8195        event.dataTransfer.effectAllowed = "move";
   8196        event.dataTransfer.setData("text/topsite-index", this.props.index);
   8197        this.props.onDragEvent(event, this.props.index, this.props.link, this.props.title);
   8198        break;
   8199      case "dragend":
   8200        this.props.onDragEvent(event);
   8201        break;
   8202      case "dragenter":
   8203      case "dragover":
   8204      case "drop":
   8205        if (this._allowDrop(event)) {
   8206          event.preventDefault();
   8207          this.props.onDragEvent(event, this.props.index);
   8208        }
   8209        break;
   8210      case "mousedown":
   8211        // Block the scroll wheel from appearing for middle clicks on search top sites
   8212        if (event.button === 1 && this.props.link.searchTopSite) {
   8213          event.preventDefault();
   8214        }
   8215        // Reset at the first mouse event of a potential drag
   8216        this.dragged = false;
   8217        break;
   8218    }
   8219  }
   8220 
   8221  /**
   8222   * Helper to obtain the next state based on nextProps and prevState.
   8223   *
   8224   * NOTE: Rename this method to getDerivedStateFromProps when we update React
   8225   *       to >= 16.3. We will need to update tests as well. We cannot rename this
   8226   *       method to getDerivedStateFromProps now because there is a mismatch in
   8227   *       the React version that we are using for both testing and production.
   8228   *       (i.e. react-test-render => "16.3.2", react => "16.2.0").
   8229   *
   8230   * See https://github.com/airbnb/enzyme/blob/master/packages/enzyme-adapter-react-16/package.json#L43.
   8231   */
   8232  static getNextStateFromProps(nextProps, prevState) {
   8233    const {
   8234      screenshot
   8235    } = nextProps.link;
   8236    const imageInState = ScreenshotUtils.isRemoteImageLocal(prevState.screenshotImage, screenshot);
   8237    if (imageInState) {
   8238      return null;
   8239    }
   8240 
   8241    // Since image was updated, attempt to revoke old image blob URL, if it exists.
   8242    ScreenshotUtils.maybeRevokeBlobObjectURL(prevState.screenshotImage);
   8243    return {
   8244      screenshotImage: ScreenshotUtils.createLocalImageObject(screenshot)
   8245    };
   8246  }
   8247 
   8248  // NOTE: Remove this function when we update React to >= 16.3 since React will
   8249  //       call getDerivedStateFromProps automatically. We will also need to
   8250  //       rename getNextStateFromProps to getDerivedStateFromProps.
   8251  componentWillMount() {
   8252    const nextState = TopSiteLink.getNextStateFromProps(this.props, this.state);
   8253    if (nextState) {
   8254      this.setState(nextState);
   8255    }
   8256  }
   8257 
   8258  // NOTE: Remove this function when we update React to >= 16.3 since React will
   8259  //       call getDerivedStateFromProps automatically. We will also need to
   8260  //       rename getNextStateFromProps to getDerivedStateFromProps.
   8261  componentWillReceiveProps(nextProps) {
   8262    const nextState = TopSiteLink.getNextStateFromProps(nextProps, this.state);
   8263    if (nextState) {
   8264      this.setState(nextState);
   8265    }
   8266  }
   8267  componentWillUnmount() {
   8268    ScreenshotUtils.maybeRevokeBlobObjectURL(this.state.screenshotImage);
   8269  }
   8270  onKeyPress(event) {
   8271    // If we have tabbed to a search shortcut top site, and we click 'enter',
   8272    // we should execute the onClick function. This needs to be added because
   8273    // search top sites are anchor tags without an href. See bug 1483135
   8274    if (event.key === "Enter" && (this.props.link.searchTopSite || this.props.isAddButton)) {
   8275      this.props.onClick(event);
   8276    }
   8277  }
   8278 
   8279  /*
   8280   * Takes the url as a string, runs it through a simple (non-secure) hash turning it into a random number
   8281   * Apply that random number to the color array. The same url will always generate the same color.
   8282   */
   8283  generateColor() {
   8284    let {
   8285      title,
   8286      colors
   8287    } = this.props;
   8288    if (!colors) {
   8289      return "";
   8290    }
   8291    let colorArray = colors.split(",");
   8292    const hashStr = str => {
   8293      let hash = 0;
   8294      for (let i = 0; i < str.length; i++) {
   8295        let charCode = str.charCodeAt(i);
   8296        hash += charCode;
   8297      }
   8298      return hash;
   8299    };
   8300    let hash = hashStr(title);
   8301    let index = hash % colorArray.length;
   8302    return colorArray[index];
   8303  }
   8304  calculateStyle() {
   8305    const {
   8306      defaultStyle,
   8307      link
   8308    } = this.props;
   8309    const {
   8310      tippyTopIcon,
   8311      faviconSize
   8312    } = link;
   8313    let imageClassName;
   8314    let imageStyle;
   8315    let showSmallFavicon = false;
   8316    let smallFaviconStyle;
   8317    let hasScreenshotImage = this.state.screenshotImage && this.state.screenshotImage.url;
   8318    let selectedColor;
   8319    if (defaultStyle) {
   8320      // force no styles (letter fallback) even if the link has imagery
   8321      selectedColor = this.generateColor();
   8322    } else if (link.searchTopSite) {
   8323      imageClassName = "top-site-icon rich-icon";
   8324      imageStyle = {
   8325        backgroundColor: link.backgroundColor,
   8326        backgroundImage: `url(${tippyTopIcon})`
   8327      };
   8328      smallFaviconStyle = {
   8329        backgroundImage: `url(${tippyTopIcon})`
   8330      };
   8331    } else if (link.customScreenshotURL) {
   8332      // assume high quality custom screenshot and use rich icon styles and class names
   8333      imageClassName = "top-site-icon rich-icon";
   8334      imageStyle = {
   8335        backgroundColor: link.backgroundColor,
   8336        backgroundImage: hasScreenshotImage ? `url(${this.state.screenshotImage.url})` : ""
   8337      };
   8338    } else if (tippyTopIcon || link.type === SPOC_TYPE || faviconSize >= MIN_RICH_FAVICON_SIZE) {
   8339      // styles and class names for top sites with rich icons
   8340      imageClassName = "top-site-icon rich-icon";
   8341      imageStyle = {
   8342        backgroundColor: link.backgroundColor,
   8343        backgroundImage: `url(${tippyTopIcon || link.favicon})`
   8344      };
   8345    } else if (faviconSize >= MIN_SMALL_FAVICON_SIZE) {
   8346      showSmallFavicon = true;
   8347      smallFaviconStyle = {
   8348        backgroundImage: `url(${link.favicon})`
   8349      };
   8350    } else {
   8351      selectedColor = this.generateColor();
   8352      imageClassName = "";
   8353    }
   8354    return {
   8355      showSmallFavicon,
   8356      smallFaviconStyle,
   8357      imageStyle,
   8358      imageClassName,
   8359      selectedColor
   8360    };
   8361  }
   8362  shouldShowOMCHighlight(componentId) {
   8363    const messageData = this.props.Messages?.messageData;
   8364    if (!messageData || Object.keys(messageData).length === 0) {
   8365      return false;
   8366    }
   8367    return messageData?.content?.messageType === componentId;
   8368  }
   8369  render() {
   8370    const {
   8371      children,
   8372      className,
   8373      isDraggable,
   8374      link,
   8375      onClick,
   8376      title,
   8377      isAddButton,
   8378      visibleTopSites
   8379    } = this.props;
   8380    const topSiteOuterClassName = `top-site-outer${className ? ` ${className}` : ""}${link.isDragged ? " dragged" : ""}${link.searchTopSite ? " search-shortcut" : ""}`;
   8381    const [letterFallback] = title;
   8382    const {
   8383      showSmallFavicon,
   8384      smallFaviconStyle,
   8385      imageStyle,
   8386      imageClassName,
   8387      selectedColor
   8388    } = this.calculateStyle();
   8389    const addButtonLabell10n = {
   8390      "data-l10n-id": "newtab-topsites-add-shortcut-label"
   8391    };
   8392    const addButtonTitlel10n = {
   8393      "data-l10n-id": "newtab-topsites-add-shortcut-title"
   8394    };
   8395    const addPinnedTitlel10n = {
   8396      "data-l10n-id": "topsite-label-pinned",
   8397      "data-l10n-args": JSON.stringify({
   8398        title
   8399      })
   8400    };
   8401    let draggableProps = {};
   8402    if (isDraggable) {
   8403      draggableProps = {
   8404        onClick: this.onDragEvent,
   8405        onDragEnd: this.onDragEvent,
   8406        onDragStart: this.onDragEvent,
   8407        onMouseDown: this.onDragEvent
   8408      };
   8409    }
   8410    let impressionStats = null;
   8411    if (link.type === SPOC_TYPE) {
   8412      // Record impressions for Pocket tiles.
   8413      impressionStats = /*#__PURE__*/external_React_default().createElement(ImpressionStats_ImpressionStats, {
   8414        flightId: link.flightId,
   8415        rows: [{
   8416          id: link.id,
   8417          pos: link.pos,
   8418          shim: link.shim && link.shim.impression,
   8419          advertiser: title.toLocaleLowerCase()
   8420        }],
   8421        dispatch: this.props.dispatch,
   8422        source: TOP_SITES_SOURCE
   8423      });
   8424    } else if (isSponsored(link)) {
   8425      // Record impressions for non-Pocket sponsored tiles.
   8426      impressionStats = /*#__PURE__*/external_React_default().createElement(TopSiteImpressionWrapper, {
   8427        actionType: actionTypes.TOP_SITES_SPONSORED_IMPRESSION_STATS,
   8428        tile: {
   8429          position: this.props.index,
   8430          tile_id: link.sponsored_tile_id || -1,
   8431          reporting_url: link.sponsored_impression_url,
   8432          advertiser: title.toLocaleLowerCase(),
   8433          source: NEWTAB_SOURCE,
   8434          visible_topsites: visibleTopSites,
   8435          frecency_boosted: link.type === "frecency-boost",
   8436          attribution: link.attribution
   8437        }
   8438        // For testing.
   8439        ,
   8440        IntersectionObserver: this.props.IntersectionObserver,
   8441        document: this.props.document,
   8442        dispatch: this.props.dispatch
   8443      });
   8444    } else {
   8445      // Record impressions for organic tiles.
   8446      impressionStats = /*#__PURE__*/external_React_default().createElement(TopSiteImpressionWrapper, {
   8447        actionType: actionTypes.TOP_SITES_ORGANIC_IMPRESSION_STATS,
   8448        tile: {
   8449          position: this.props.index,
   8450          source: NEWTAB_SOURCE,
   8451          isPinned: this.props.link.isPinned,
   8452          guid: this.props.link.guid,
   8453          visible_topsites: visibleTopSites,
   8454          smartScores: this.props.link.scores,
   8455          smartWeights: this.props.link.weights
   8456        }
   8457        // For testing.
   8458        ,
   8459        IntersectionObserver: this.props.IntersectionObserver,
   8460        document: this.props.document,
   8461        dispatch: this.props.dispatch
   8462      });
   8463    }
   8464    return /*#__PURE__*/external_React_default().createElement("li", TopSite_extends({
   8465      className: topSiteOuterClassName,
   8466      onDrop: this.onDragEvent,
   8467      onDragOver: this.onDragEvent,
   8468      onDragEnter: this.onDragEvent,
   8469      onDragLeave: this.onDragEvent,
   8470      ref: this.props.setRef
   8471    }, draggableProps), /*#__PURE__*/external_React_default().createElement("div", {
   8472      className: "top-site-inner"
   8473    }, /*#__PURE__*/external_React_default().createElement("a", TopSite_extends({
   8474      className: "top-site-button",
   8475      href: link.searchTopSite ? undefined : link.url,
   8476      tabIndex: this.props.tabIndex,
   8477      onKeyPress: this.onKeyPress,
   8478      onClick: onClick,
   8479      draggable: true,
   8480      "data-is-sponsored-link": !!link.sponsored_tile_id,
   8481      onFocus: this.props.onFocus,
   8482      "aria-label": link.isPinned ? undefined : title
   8483    }, isAddButton && {
   8484      ...addButtonTitlel10n
   8485    }, !isAddButton && {
   8486      title
   8487    }, link.isPinned && {
   8488      ...addPinnedTitlel10n
   8489    }, {
   8490      "data-l10n-args": JSON.stringify({
   8491        title
   8492      })
   8493    }), link.isPinned && /*#__PURE__*/external_React_default().createElement("div", {
   8494      className: "icon icon-pin-small"
   8495    }), /*#__PURE__*/external_React_default().createElement("div", {
   8496      className: "tile",
   8497      "aria-hidden": true
   8498    }, /*#__PURE__*/external_React_default().createElement("div", {
   8499      className: selectedColor ? "icon-wrapper letter-fallback" : "icon-wrapper",
   8500      "data-fallback": letterFallback,
   8501      style: selectedColor ? {
   8502        backgroundColor: selectedColor
   8503      } : {}
   8504    }, /*#__PURE__*/external_React_default().createElement("div", {
   8505      className: imageClassName,
   8506      style: imageStyle
   8507    }), showSmallFavicon && /*#__PURE__*/external_React_default().createElement("div", {
   8508      className: "top-site-icon default-icon",
   8509      "data-fallback": smallFaviconStyle ? "" : letterFallback,
   8510      style: smallFaviconStyle
   8511    }))), /*#__PURE__*/external_React_default().createElement("div", {
   8512      className: `title${link.isPinned ? " has-icon pinned" : ""}${link.type === SPOC_TYPE || link.show_sponsored_label ? " sponsored" : ""}`
   8513    }, /*#__PURE__*/external_React_default().createElement("span", TopSite_extends({
   8514      className: "title-label",
   8515      dir: "auto"
   8516    }, isAddButton && {
   8517      ...addButtonLabell10n
   8518    }), link.searchTopSite && /*#__PURE__*/external_React_default().createElement("div", {
   8519      className: "top-site-icon search-topsite"
   8520    }), title || /*#__PURE__*/external_React_default().createElement("br", null)), /*#__PURE__*/external_React_default().createElement("span", {
   8521      className: "sponsored-label",
   8522      "data-l10n-id": "newtab-topsite-sponsored"
   8523    }))), isAddButton && this.shouldShowOMCHighlight("ShortcutHighlight") && /*#__PURE__*/external_React_default().createElement(MessageWrapper, {
   8524      dispatch: this.props.dispatch,
   8525      onClick: e => e.stopPropagation()
   8526    }, /*#__PURE__*/external_React_default().createElement(ShortcutFeatureHighlight, {
   8527      dispatch: this.props.dispatch,
   8528      feature: "FEATURE_SHORTCUT_HIGHLIGHT",
   8529      position: "inset-block-end inset-inline-start",
   8530      messageData: this.props.Messages?.messageData
   8531    })), children, impressionStats));
   8532  }
   8533 }
   8534 TopSiteLink.defaultProps = {
   8535  title: "",
   8536  link: {},
   8537  isDraggable: true
   8538 };
   8539 class TopSite extends (external_React_default()).PureComponent {
   8540  constructor(props) {
   8541    super(props);
   8542    this.state = {
   8543      showContextMenu: false
   8544    };
   8545    this.onLinkClick = this.onLinkClick.bind(this);
   8546    this.onMenuUpdate = this.onMenuUpdate.bind(this);
   8547  }
   8548 
   8549  /**
   8550   * Report to telemetry additional information about the item.
   8551   */
   8552  _getTelemetryInfo() {
   8553    const value = {
   8554      icon_type: this.props.link.iconType
   8555    };
   8556    // Filter out "not_pinned" type for being the default
   8557    if (this.props.link.isPinned) {
   8558      value.card_type = "pinned";
   8559    }
   8560    if (this.props.link.searchTopSite) {
   8561      // Set the card_type as "search" regardless of its pinning status
   8562      value.card_type = "search";
   8563      value.search_vendor = this.props.link.hostname;
   8564    }
   8565    if (isSponsored(this.props.link)) {
   8566      value.card_type = "spoc";
   8567    }
   8568    return {
   8569      value
   8570    };
   8571  }
   8572  userEvent(event) {
   8573    this.props.dispatch(actionCreators.UserEvent(Object.assign({
   8574      event,
   8575      source: TOP_SITES_SOURCE,
   8576      action_position: this.props.index
   8577    }, this._getTelemetryInfo())));
   8578  }
   8579  onLinkClick(event) {
   8580    this.userEvent("CLICK");
   8581 
   8582    // Specially handle a top site link click for "typed" frecency bonus as
   8583    // specified as a property on the link.
   8584    event.preventDefault();
   8585    const {
   8586      altKey,
   8587      button,
   8588      ctrlKey,
   8589      metaKey,
   8590      shiftKey
   8591    } = event;
   8592    if (!this.props.link.searchTopSite) {
   8593      this.props.dispatch(actionCreators.OnlyToMain({
   8594        type: actionTypes.OPEN_LINK,
   8595        data: Object.assign(this.props.link, {
   8596          event: {
   8597            altKey,
   8598            button,
   8599            ctrlKey,
   8600            metaKey,
   8601            shiftKey
   8602          },
   8603          is_sponsored: !!this.props.link.sponsored_tile_id
   8604        })
   8605      }));
   8606      if (this.props.link.type === SPOC_TYPE) {
   8607        // Record a Pocket-specific click.
   8608        this.props.dispatch(actionCreators.ImpressionStats({
   8609          source: TOP_SITES_SOURCE,
   8610          click: 0,
   8611          tiles: [{
   8612            id: this.props.link.id,
   8613            pos: this.props.link.pos,
   8614            shim: this.props.link.shim && this.props.link.shim.click
   8615          }]
   8616        }));
   8617 
   8618        // Record a click for a Pocket sponsored tile.
   8619        // This first event is for the shim property
   8620        // and is used by our ad service provider.
   8621        this.props.dispatch(actionCreators.DiscoveryStreamUserEvent({
   8622          event: "CLICK",
   8623          source: TOP_SITES_SOURCE,
   8624          action_position: this.props.link.pos,
   8625          value: {
   8626            card_type: "spoc",
   8627            tile_id: this.props.link.id,
   8628            shim: this.props.link.shim && this.props.link.shim.click,
   8629            attribution: this.props.link.attribution
   8630          }
   8631        }));
   8632 
   8633        // A second event is recoded for internal usage.
   8634        const title = this.props.link.label || this.props.link.hostname;
   8635        this.props.dispatch(actionCreators.OnlyToMain({
   8636          type: actionTypes.TOP_SITES_SPONSORED_IMPRESSION_STATS,
   8637          data: {
   8638            type: "click",
   8639            position: this.props.link.pos,
   8640            tile_id: this.props.link.id,
   8641            advertiser: title.toLocaleLowerCase(),
   8642            source: NEWTAB_SOURCE,
   8643            attribution: this.props.link.attribution
   8644          }
   8645        }));
   8646      } else if (isSponsored(this.props.link)) {
   8647        // Record a click for a non-Pocket sponsored tile.
   8648        const title = this.props.link.label || this.props.link.hostname;
   8649        this.props.dispatch(actionCreators.OnlyToMain({
   8650          type: actionTypes.TOP_SITES_SPONSORED_IMPRESSION_STATS,
   8651          data: {
   8652            type: "click",
   8653            position: this.props.index,
   8654            tile_id: this.props.link.sponsored_tile_id || -1,
   8655            reporting_url: this.props.link.sponsored_click_url,
   8656            advertiser: title.toLocaleLowerCase(),
   8657            source: NEWTAB_SOURCE,
   8658            visible_topsites: this.props.visibleTopSites,
   8659            frecency_boosted: this.props.link.type === "frecency-boost",
   8660            attribution: this.props.link.attribution
   8661          }
   8662        }));
   8663      } else {
   8664        // Record a click for an organic tile.
   8665        this.props.dispatch(actionCreators.OnlyToMain({
   8666          type: actionTypes.TOP_SITES_ORGANIC_IMPRESSION_STATS,
   8667          data: {
   8668            type: "click",
   8669            position: this.props.index,
   8670            source: NEWTAB_SOURCE,
   8671            isPinned: this.props.link.isPinned,
   8672            guid: this.props.link.guid,
   8673            visible_topsites: this.props.visibleTopSites,
   8674            smartScores: this.props.link.scores,
   8675            smartWeights: this.props.link.weights
   8676          }
   8677        }));
   8678      }
   8679      if (this.props.link.sendAttributionRequest) {
   8680        this.props.dispatch(actionCreators.OnlyToMain({
   8681          type: actionTypes.PARTNER_LINK_ATTRIBUTION,
   8682          data: {
   8683            targetURL: this.props.link.url,
   8684            source: "newtab"
   8685          }
   8686        }));
   8687      }
   8688    } else {
   8689      this.props.dispatch(actionCreators.OnlyToMain({
   8690        type: actionTypes.FILL_SEARCH_TERM,
   8691        data: {
   8692          label: this.props.link.label
   8693        }
   8694      }));
   8695    }
   8696  }
   8697  onMenuUpdate(isOpen) {
   8698    if (isOpen) {
   8699      this.props.onActivate(this.props.index);
   8700    } else {
   8701      this.props.onActivate();
   8702    }
   8703  }
   8704  render() {
   8705    const {
   8706      props
   8707    } = this;
   8708    const {
   8709      link
   8710    } = props;
   8711    const isContextMenuOpen = props.activeIndex === props.index;
   8712    const title = link.label || link.title || link.hostname;
   8713    let menuOptions;
   8714    if (link.sponsored_position) {
   8715      menuOptions = TOP_SITES_SPONSORED_POSITION_CONTEXT_MENU_OPTIONS;
   8716    } else if (link.searchTopSite) {
   8717      menuOptions = TOP_SITES_SEARCH_SHORTCUTS_CONTEXT_MENU_OPTIONS;
   8718    } else if (link.type === SPOC_TYPE) {
   8719      menuOptions = TOP_SITES_SPOC_CONTEXT_MENU_OPTIONS;
   8720    } else {
   8721      menuOptions = TOP_SITES_CONTEXT_MENU_OPTIONS;
   8722    }
   8723    return /*#__PURE__*/external_React_default().createElement(TopSiteLink, TopSite_extends({}, props, {
   8724      onClick: this.onLinkClick,
   8725      onDragEvent: this.props.onDragEvent,
   8726      className: `${props.className || ""}${isContextMenuOpen ? " active" : ""}`,
   8727      title: title,
   8728      setPref: this.props.setPref,
   8729      tabIndex: this.props.tabIndex,
   8730      onFocus: this.props.onFocus
   8731    }), /*#__PURE__*/external_React_default().createElement("div", null, /*#__PURE__*/external_React_default().createElement(ContextMenuButton, {
   8732      tooltip: "newtab-menu-content-tooltip",
   8733      tooltipArgs: {
   8734        title
   8735      },
   8736      onUpdate: this.onMenuUpdate,
   8737      tabIndex: this.props.tabIndex,
   8738      onFocus: this.props.onFocus
   8739    }, /*#__PURE__*/external_React_default().createElement(LinkMenu, {
   8740      dispatch: props.dispatch,
   8741      index: props.index,
   8742      onUpdate: this.onMenuUpdate,
   8743      options: menuOptions,
   8744      site: link,
   8745      shouldSendImpressionStats: link.type === SPOC_TYPE,
   8746      siteInfo: this._getTelemetryInfo(),
   8747      source: TOP_SITES_SOURCE
   8748    }))));
   8749  }
   8750 }
   8751 TopSite.defaultProps = {
   8752  link: {},
   8753  onActivate() {}
   8754 };
   8755 class TopSiteAddButton extends (external_React_default()).PureComponent {
   8756  constructor(props) {
   8757    super(props);
   8758    this.onEditButtonClick = this.onEditButtonClick.bind(this);
   8759  }
   8760  onEditButtonClick() {
   8761    this.props.dispatch({
   8762      type: actionTypes.TOP_SITES_EDIT,
   8763      data: {
   8764        index: this.props.index
   8765      }
   8766    });
   8767  }
   8768  render() {
   8769    return /*#__PURE__*/external_React_default().createElement(TopSiteLink, TopSite_extends({}, this.props, {
   8770      isAddButton: true,
   8771      className: `add-button ${this.props.className || ""}`,
   8772      onClick: this.onEditButtonClick,
   8773      setPref: this.props.setPref,
   8774      isDraggable: false,
   8775      tabIndex: this.props.tabIndex
   8776    }));
   8777  }
   8778 }
   8779 class TopSitePlaceholder extends (external_React_default()).PureComponent {
   8780  render() {
   8781    return /*#__PURE__*/external_React_default().createElement(TopSiteLink, TopSite_extends({}, this.props, {
   8782      className: `placeholder ${this.props.className || ""}`,
   8783      isDraggable: false
   8784    }));
   8785  }
   8786 }
   8787 class _TopSiteList extends (external_React_default()).PureComponent {
   8788  static get DEFAULT_STATE() {
   8789    return {
   8790      activeIndex: null,
   8791      draggedIndex: null,
   8792      draggedSite: null,
   8793      draggedTitle: null,
   8794      topSitesPreview: null,
   8795      focusedIndex: 0
   8796    };
   8797  }
   8798  constructor(props) {
   8799    super(props);
   8800    this.state = _TopSiteList.DEFAULT_STATE;
   8801    this.onDragEvent = this.onDragEvent.bind(this);
   8802    this.onActivate = this.onActivate.bind(this);
   8803    this.onWrapperFocus = this.onWrapperFocus.bind(this);
   8804    this.onTopsiteFocus = this.onTopsiteFocus.bind(this);
   8805    this.onWrapperBlur = this.onWrapperBlur.bind(this);
   8806    this.onKeyDown = this.onKeyDown.bind(this);
   8807  }
   8808  componentWillReceiveProps(nextProps) {
   8809    if (this.state.draggedSite) {
   8810      const prevTopSites = this.props.TopSites && this.props.TopSites.rows;
   8811      const newTopSites = nextProps.TopSites && nextProps.TopSites.rows;
   8812      if (prevTopSites && prevTopSites[this.state.draggedIndex] && prevTopSites[this.state.draggedIndex].url === this.state.draggedSite.url && (!newTopSites[this.state.draggedIndex] || newTopSites[this.state.draggedIndex].url !== this.state.draggedSite.url)) {
   8813        // We got the new order from the redux store via props. We can clear state now.
   8814        this.setState(_TopSiteList.DEFAULT_STATE);
   8815      }
   8816    }
   8817  }
   8818  userEvent(event, index) {
   8819    this.props.dispatch(actionCreators.UserEvent({
   8820      event,
   8821      source: TOP_SITES_SOURCE,
   8822      action_position: index
   8823    }));
   8824  }
   8825  onDragEvent(event, index, link, title) {
   8826    switch (event.type) {
   8827      case "dragstart":
   8828        this.dropped = false;
   8829        this.setState({
   8830          draggedIndex: index,
   8831          draggedSite: link,
   8832          draggedTitle: title,
   8833          activeIndex: null
   8834        });
   8835        this.userEvent("DRAG", index);
   8836        break;
   8837      case "dragend":
   8838        if (!this.dropped) {
   8839          // If there was no drop event, reset the state to the default.
   8840          this.setState(_TopSiteList.DEFAULT_STATE);
   8841        }
   8842        break;
   8843      case "dragenter":
   8844        if (index === this.state.draggedIndex) {
   8845          this.setState({
   8846            topSitesPreview: null
   8847          });
   8848        } else {
   8849          this.setState({
   8850            topSitesPreview: this._makeTopSitesPreview(index)
   8851          });
   8852        }
   8853        break;
   8854      case "drop":
   8855        if (index !== this.state.draggedIndex) {
   8856          this.dropped = true;
   8857          this.props.dispatch(actionCreators.AlsoToMain({
   8858            type: actionTypes.TOP_SITES_INSERT,
   8859            data: {
   8860              site: {
   8861                url: this.state.draggedSite.url,
   8862                label: this.state.draggedTitle,
   8863                customScreenshotURL: this.state.draggedSite.customScreenshotURL,
   8864                // Only if the search topsites experiment is enabled
   8865                ...(this.state.draggedSite.searchTopSite && {
   8866                  searchTopSite: true
   8867                })
   8868              },
   8869              index,
   8870              draggedFromIndex: this.state.draggedIndex
   8871            }
   8872          }));
   8873          this.userEvent("DROP", index);
   8874        }
   8875        break;
   8876    }
   8877  }
   8878  _getTopSites() {
   8879    // Make a copy of the sites to truncate or extend to desired length
   8880    let topSites = this.props.TopSites.rows.slice();
   8881    topSites.length = this.props.TopSitesRows * TOP_SITES_MAX_SITES_PER_ROW;
   8882    // if topSites do not fill an entire row add 'Add shortcut' button to array of topSites
   8883    // (there should only be one of these)
   8884    let firstPlaceholder = topSites.findIndex(Object.is.bind(null, undefined));
   8885    // make sure placeholder exists and there already isnt a add button
   8886    if (firstPlaceholder && !topSites.includes(site => site.isAddButton)) {
   8887      topSites[firstPlaceholder] = {
   8888        isAddButton: true
   8889      };
   8890    } else if (topSites.includes(site => site.isAddButton)) {
   8891      topSites.push(topSites.splice(topSites.indexOf({
   8892        isAddButton: true
   8893      }), 1)[0]);
   8894    }
   8895    return topSites;
   8896  }
   8897 
   8898  /**
   8899   * Make a preview of the topsites that will be the result of dropping the currently
   8900   * dragged site at the specified index.
   8901   */
   8902  _makeTopSitesPreview(index) {
   8903    const topSites = this._getTopSites();
   8904    topSites[this.state.draggedIndex] = null;
   8905    const preview = topSites.map(site => site && (site.isPinned || isSponsored(site)) ? site : null);
   8906    const unpinned = topSites.filter(site => site && !site.isPinned && !isSponsored(site));
   8907    const siteToInsert = Object.assign({}, this.state.draggedSite, {
   8908      isPinned: true,
   8909      isDragged: true
   8910    });
   8911    if (!preview[index]) {
   8912      preview[index] = siteToInsert;
   8913    } else {
   8914      // Find the hole to shift the pinned site(s) towards. We shift towards the
   8915      // hole left by the site being dragged.
   8916      let holeIndex = index;
   8917      const indexStep = index > this.state.draggedIndex ? -1 : 1;
   8918      while (preview[holeIndex]) {
   8919        holeIndex += indexStep;
   8920      }
   8921 
   8922      // Shift towards the hole.
   8923      const shiftingStep = index > this.state.draggedIndex ? 1 : -1;
   8924      while (index > this.state.draggedIndex ? holeIndex < index : holeIndex > index) {
   8925        let nextIndex = holeIndex + shiftingStep;
   8926        while (isSponsored(preview[nextIndex])) {
   8927          nextIndex += shiftingStep;
   8928        }
   8929        preview[holeIndex] = preview[nextIndex];
   8930        holeIndex = nextIndex;
   8931      }
   8932      preview[index] = siteToInsert;
   8933    }
   8934 
   8935    // Fill in the remaining holes with unpinned sites.
   8936    for (let i = 0; i < preview.length; i++) {
   8937      if (!preview[i]) {
   8938        preview[i] = unpinned.shift() || null;
   8939      }
   8940    }
   8941    return preview;
   8942  }
   8943  onActivate(index) {
   8944    this.setState({
   8945      activeIndex: index
   8946    });
   8947  }
   8948  onKeyDown(e) {
   8949    if (this.state.activeIndex || this.state.activeIndex === 0) {
   8950      return;
   8951    }
   8952    if (e.key === "ArrowLeft" || e.key === "ArrowRight") {
   8953      // Arrow direction should match visual navigation direction in RTL
   8954      const isRTL = document.dir === "rtl";
   8955      const navigateToPrevious = isRTL ? e.key === "ArrowRight" : e.key === "ArrowLeft";
   8956      const targetTopSite = navigateToPrevious ? this.focusedRef?.previousSibling : this.focusedRef?.nextSibling;
   8957      const targetAnchor = targetTopSite?.querySelector("a");
   8958      if (targetAnchor) {
   8959        targetAnchor.tabIndex = 0;
   8960        targetAnchor.focus();
   8961      }
   8962    }
   8963  }
   8964  onWrapperFocus() {
   8965    this.focusRef?.addEventListener("keydown", this.onKeyDown);
   8966  }
   8967  onWrapperBlur() {
   8968    this.focusRef?.removeEventListener("keydown", this.onKeyDown);
   8969  }
   8970  onTopsiteFocus(focusIndex) {
   8971    this.setState(() => ({
   8972      focusedIndex: focusIndex
   8973    }));
   8974  }
   8975  render() {
   8976    const {
   8977      props
   8978    } = this;
   8979    const topSites = this.state.topSitesPreview || this._getTopSites();
   8980    const topSitesUI = [];
   8981    const commonProps = {
   8982      onDragEvent: this.onDragEvent,
   8983      dispatch: props.dispatch
   8984    };
   8985    // We assign a key to each placeholder slot. We need it to be independent
   8986    // of the slot index (i below) so that the keys used stay the same during
   8987    // drag and drop reordering and the underlying DOM nodes are reused.
   8988    // This mostly (only?) affects linux so be sure to test on linux before changing.
   8989    let holeIndex = 0;
   8990 
   8991    // On narrow viewports, we only show 6 sites per row. We'll mark the rest as
   8992    // .hide-for-narrow to hide in CSS via @media query.
   8993    const maxNarrowVisibleIndex = props.TopSitesRows * 6;
   8994    for (let i = 0, l = topSites.length; i < l; i++) {
   8995      const link = topSites[i] && Object.assign({}, topSites[i], {
   8996        iconType: this.props.topSiteIconType(topSites[i])
   8997      });
   8998      const slotProps = {
   8999        key: link ? link.url : holeIndex++,
   9000        index: i
   9001      };
   9002      if (i >= maxNarrowVisibleIndex) {
   9003        slotProps.className = "hide-for-narrow";
   9004      }
   9005      let topSiteLink;
   9006      // Use a placeholder if the link is empty or it's rendering a sponsored
   9007      // tile for the about:home startup cache.
   9008      if (!link || props.App.isForStartupCache.TopSites && isSponsored(link)) {
   9009        if (link) {
   9010          topSiteLink = /*#__PURE__*/external_React_default().createElement(TopSitePlaceholder, TopSite_extends({}, slotProps, commonProps));
   9011        }
   9012      } else if (topSites[i]?.isAddButton) {
   9013        topSiteLink = /*#__PURE__*/external_React_default().createElement(TopSiteAddButton, TopSite_extends({}, slotProps, commonProps, {
   9014          setRef: i === this.state.focusedIndex ? el => {
   9015            this.focusedRef = el;
   9016          } : () => {},
   9017          tabIndex: i === this.state.focusedIndex ? 0 : -1,
   9018          onFocus: () => {
   9019            this.onTopsiteFocus(i);
   9020          },
   9021          Messages: this.props.Messages,
   9022          visibleTopSites: this.props.visibleTopSites
   9023        }));
   9024      } else {
   9025        topSiteLink = /*#__PURE__*/external_React_default().createElement(TopSite, TopSite_extends({
   9026          link: link,
   9027          activeIndex: this.state.activeIndex,
   9028          onActivate: this.onActivate
   9029        }, slotProps, commonProps, {
   9030          colors: props.colors,
   9031          setRef: i === this.state.focusedIndex ? el => {
   9032            this.focusedRef = el;
   9033          } : () => {},
   9034          tabIndex: i === this.state.focusedIndex ? 0 : -1,
   9035          onFocus: () => {
   9036            this.onTopsiteFocus(i);
   9037          },
   9038          visibleTopSites: this.props.visibleTopSites
   9039        }));
   9040      }
   9041      topSitesUI.push(topSiteLink);
   9042    }
   9043    return /*#__PURE__*/external_React_default().createElement("div", {
   9044      className: "top-sites-list-wrapper"
   9045    }, /*#__PURE__*/external_React_default().createElement("ul", {
   9046      role: "group",
   9047      "aria-label": "Shortcuts",
   9048      onFocus: this.onWrapperFocus,
   9049      onBlur: this.onWrapperBlur,
   9050      ref: el => {
   9051        this.focusRef = el;
   9052      },
   9053      className: `top-sites-list${this.state.draggedSite ? " dnd-active" : ""}`
   9054    }, topSitesUI));
   9055  }
   9056 }
   9057 const TopSiteList = (0,external_ReactRedux_namespaceObject.connect)(state => ({
   9058  App: state.App,
   9059  Messages: state.Messages,
   9060  Prefs: state.Prefs
   9061 }))(_TopSiteList);
   9062 ;// CONCATENATED MODULE: ./content-src/components/TopSites/TopSiteForm.jsx
   9063 /* This Source Code Form is subject to the terms of the Mozilla Public
   9064 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   9065 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   9066 
   9067 
   9068 
   9069 
   9070 
   9071 
   9072 
   9073 class TopSiteForm extends (external_React_default()).PureComponent {
   9074  constructor(props) {
   9075    super(props);
   9076    const {
   9077      site
   9078    } = props;
   9079    this.state = {
   9080      label: site ? site.label || site.hostname : "",
   9081      url: site ? site.url : "",
   9082      validationError: false,
   9083      customScreenshotUrl: site ? site.customScreenshotURL : "",
   9084      showCustomScreenshotForm: site ? site.customScreenshotURL : false,
   9085      hasURLChanged: false,
   9086      hasTitleChanged: false
   9087    };
   9088    this.onClearScreenshotInput = this.onClearScreenshotInput.bind(this);
   9089    this.onLabelChange = this.onLabelChange.bind(this);
   9090    this.onUrlChange = this.onUrlChange.bind(this);
   9091    this.onCancelButtonClick = this.onCancelButtonClick.bind(this);
   9092    this.onClearUrlClick = this.onClearUrlClick.bind(this);
   9093    this.onDoneButtonClick = this.onDoneButtonClick.bind(this);
   9094    this.onCustomScreenshotUrlChange = this.onCustomScreenshotUrlChange.bind(this);
   9095    this.onPreviewButtonClick = this.onPreviewButtonClick.bind(this);
   9096    this.onEnableScreenshotUrlForm = this.onEnableScreenshotUrlForm.bind(this);
   9097    this.validateUrl = this.validateUrl.bind(this);
   9098  }
   9099  onLabelChange(event) {
   9100    this.setState({
   9101      label: event.target.value,
   9102      hasTitleChanged: true
   9103    });
   9104  }
   9105  onUrlChange(event) {
   9106    this.setState({
   9107      url: event.target.value,
   9108      validationError: false,
   9109      hasURLChanged: true
   9110    });
   9111  }
   9112  onClearUrlClick() {
   9113    this.setState({
   9114      url: "",
   9115      validationError: false
   9116    });
   9117  }
   9118  onEnableScreenshotUrlForm() {
   9119    this.setState({
   9120      showCustomScreenshotForm: true
   9121    });
   9122  }
   9123  _updateCustomScreenshotInput(customScreenshotUrl) {
   9124    this.setState({
   9125      customScreenshotUrl,
   9126      validationError: false
   9127    });
   9128    this.props.dispatch({
   9129      type: actionTypes.PREVIEW_REQUEST_CANCEL
   9130    });
   9131  }
   9132  onCustomScreenshotUrlChange(event) {
   9133    this._updateCustomScreenshotInput(event.target.value);
   9134  }
   9135  onClearScreenshotInput() {
   9136    this._updateCustomScreenshotInput("");
   9137  }
   9138  onCancelButtonClick(ev) {
   9139    ev.preventDefault();
   9140    this.props.onClose();
   9141  }
   9142  onDoneButtonClick(ev) {
   9143    ev.preventDefault();
   9144    if (this.validateForm()) {
   9145      const site = {
   9146        url: this.cleanUrl(this.state.url)
   9147      };
   9148      const {
   9149        index
   9150      } = this.props;
   9151      const isEdit = !!this.props.site;
   9152      if (this.state.label !== "") {
   9153        site.label = this.state.label;
   9154      }
   9155      if (this.state.customScreenshotUrl) {
   9156        site.customScreenshotURL = this.cleanUrl(this.state.customScreenshotUrl);
   9157      } else if (this.props.site && this.props.site.customScreenshotURL) {
   9158        // Used to flag that previously cached screenshot should be removed
   9159        site.customScreenshotURL = null;
   9160      }
   9161      this.props.dispatch(actionCreators.AlsoToMain({
   9162        type: actionTypes.TOP_SITES_PIN,
   9163        data: {
   9164          site,
   9165          index
   9166        }
   9167      }));
   9168      if (isEdit) {
   9169        this.props.dispatch(actionCreators.UserEvent({
   9170          source: TOP_SITES_SOURCE,
   9171          event: "TOP_SITES_EDIT",
   9172          action_position: index,
   9173          hasTitleChanged: this.state.hasTitleChanged,
   9174          hasURLChanged: this.state.hasURLChanged
   9175        }));
   9176      } else if (!isEdit) {
   9177        this.props.dispatch(actionCreators.UserEvent({
   9178          source: TOP_SITES_SOURCE,
   9179          event: "TOP_SITES_ADD",
   9180          action_position: index
   9181        }));
   9182      }
   9183      this.props.onClose();
   9184    }
   9185  }
   9186  onPreviewButtonClick(event) {
   9187    event.preventDefault();
   9188    if (this.validateForm()) {
   9189      this.props.dispatch(actionCreators.AlsoToMain({
   9190        type: actionTypes.PREVIEW_REQUEST,
   9191        data: {
   9192          url: this.cleanUrl(this.state.customScreenshotUrl)
   9193        }
   9194      }));
   9195      this.props.dispatch(actionCreators.UserEvent({
   9196        source: TOP_SITES_SOURCE,
   9197        event: "PREVIEW_REQUEST"
   9198      }));
   9199    }
   9200  }
   9201  cleanUrl(url) {
   9202    // If we are missing a protocol, prepend http://
   9203    if (!url.startsWith("http:") && !url.startsWith("https:")) {
   9204      return `http://${url}`;
   9205    }
   9206    return url;
   9207  }
   9208  _tryParseUrl(url) {
   9209    try {
   9210      return new URL(url);
   9211    } catch (e) {
   9212      return null;
   9213    }
   9214  }
   9215  validateUrl(url) {
   9216    const validProtocols = ["http:", "https:"];
   9217    const urlObj = this._tryParseUrl(url) || this._tryParseUrl(this.cleanUrl(url));
   9218    return urlObj && validProtocols.includes(urlObj.protocol);
   9219  }
   9220  validateCustomScreenshotUrl() {
   9221    const {
   9222      customScreenshotUrl
   9223    } = this.state;
   9224    return !customScreenshotUrl || this.validateUrl(customScreenshotUrl);
   9225  }
   9226  validateForm() {
   9227    const validate = this.validateUrl(this.state.url) && this.validateCustomScreenshotUrl();
   9228    if (!validate) {
   9229      this.setState({
   9230        validationError: true
   9231      });
   9232    }
   9233    return validate;
   9234  }
   9235  _renderCustomScreenshotInput() {
   9236    const {
   9237      customScreenshotUrl
   9238    } = this.state;
   9239    const requestFailed = this.props.previewResponse === "";
   9240    const validationError = this.state.validationError && !this.validateCustomScreenshotUrl() || requestFailed;
   9241    // Set focus on error if the url field is valid or when the input is first rendered and is empty
   9242    const shouldFocus = validationError && this.validateUrl(this.state.url) || !customScreenshotUrl;
   9243    const isLoading = this.props.previewResponse === null && customScreenshotUrl && this.props.previewUrl === this.cleanUrl(customScreenshotUrl);
   9244    if (!this.state.showCustomScreenshotForm) {
   9245      return /*#__PURE__*/external_React_default().createElement(A11yLinkButton, {
   9246        onClick: this.onEnableScreenshotUrlForm,
   9247        className: "enable-custom-image-input",
   9248        "data-l10n-id": "newtab-topsites-use-image-link"
   9249      });
   9250    }
   9251    return /*#__PURE__*/external_React_default().createElement("div", {
   9252      className: "custom-image-input-container"
   9253    }, /*#__PURE__*/external_React_default().createElement(TopSiteFormInput, {
   9254      errorMessageId: requestFailed ? "newtab-topsites-image-validation" : "newtab-topsites-url-validation",
   9255      loading: isLoading,
   9256      onChange: this.onCustomScreenshotUrlChange,
   9257      onClear: this.onClearScreenshotInput,
   9258      shouldFocus: shouldFocus,
   9259      typeUrl: true,
   9260      value: customScreenshotUrl,
   9261      validationError: validationError,
   9262      titleId: "newtab-topsites-image-url-label",
   9263      placeholderId: "newtab-topsites-url-input"
   9264    }));
   9265  }
   9266  render() {
   9267    const {
   9268      customScreenshotUrl
   9269    } = this.state;
   9270    const requestFailed = this.props.previewResponse === "";
   9271    // For UI purposes, editing without an existing link is "add"
   9272    const showAsAdd = !this.props.site;
   9273    const previous = this.props.site && this.props.site.customScreenshotURL || "";
   9274    const changed = customScreenshotUrl && this.cleanUrl(customScreenshotUrl) !== previous;
   9275    // Preview mode if changes were made to the custom screenshot URL and no preview was received yet
   9276    // or the request failed
   9277    const previewMode = changed && !this.props.previewResponse;
   9278    const previewLink = Object.assign({}, this.props.site);
   9279    if (this.props.previewResponse) {
   9280      previewLink.screenshot = this.props.previewResponse;
   9281      previewLink.customScreenshotURL = this.props.previewUrl;
   9282    }
   9283    // Handles the form submit so an enter press performs the correct action
   9284    const onSubmit = previewMode ? this.onPreviewButtonClick : this.onDoneButtonClick;
   9285    const addTopsitesHeaderL10nId = "newtab-topsites-add-shortcut-header";
   9286    const editTopsitesHeaderL10nId = "newtab-topsites-edit-shortcut-header";
   9287    return /*#__PURE__*/external_React_default().createElement("form", {
   9288      className: "topsite-form",
   9289      onSubmit: onSubmit
   9290    }, /*#__PURE__*/external_React_default().createElement("div", {
   9291      className: "form-input-container"
   9292    }, /*#__PURE__*/external_React_default().createElement("h3", {
   9293      className: "section-title grey-title",
   9294      "data-l10n-id": showAsAdd ? addTopsitesHeaderL10nId : editTopsitesHeaderL10nId
   9295    }), /*#__PURE__*/external_React_default().createElement("div", {
   9296      className: "fields-and-preview"
   9297    }, /*#__PURE__*/external_React_default().createElement("div", {
   9298      className: "form-wrapper"
   9299    }, /*#__PURE__*/external_React_default().createElement(TopSiteFormInput, {
   9300      onChange: this.onLabelChange,
   9301      value: this.state.label,
   9302      titleId: "newtab-topsites-title-label",
   9303      placeholderId: "newtab-topsites-title-input",
   9304      autoFocusOnOpen: true
   9305    }), /*#__PURE__*/external_React_default().createElement(TopSiteFormInput, {
   9306      onChange: this.onUrlChange,
   9307      shouldFocus: this.state.validationError && !this.validateUrl(this.state.url),
   9308      value: this.state.url,
   9309      onClear: this.onClearUrlClick,
   9310      validationError: this.state.validationError && !this.validateUrl(this.state.url),
   9311      titleId: "newtab-topsites-url-label",
   9312      typeUrl: true,
   9313      placeholderId: "newtab-topsites-url-input",
   9314      errorMessageId: "newtab-topsites-url-validation"
   9315    }), this._renderCustomScreenshotInput()), /*#__PURE__*/external_React_default().createElement(TopSiteLink, {
   9316      link: previewLink,
   9317      defaultStyle: requestFailed,
   9318      title: this.state.label
   9319    }))), /*#__PURE__*/external_React_default().createElement("section", {
   9320      className: "actions"
   9321    }, /*#__PURE__*/external_React_default().createElement("button", {
   9322      className: "cancel",
   9323      type: "button",
   9324      onClick: this.onCancelButtonClick,
   9325      "data-l10n-id": "newtab-topsites-cancel-button"
   9326    }), previewMode ? /*#__PURE__*/external_React_default().createElement("button", {
   9327      className: "done preview",
   9328      type: "submit",
   9329      "data-l10n-id": "newtab-topsites-preview-button"
   9330    }) : /*#__PURE__*/external_React_default().createElement("button", {
   9331      className: "done",
   9332      type: "submit",
   9333      "data-l10n-id": showAsAdd ? "newtab-topsites-add-button" : "newtab-topsites-save-button"
   9334    })));
   9335  }
   9336 }
   9337 TopSiteForm.defaultProps = {
   9338  site: null,
   9339  index: -1
   9340 };
   9341 ;// CONCATENATED MODULE: ./content-src/components/TopSites/TopSites.jsx
   9342 function TopSites_extends() { return TopSites_extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, TopSites_extends.apply(null, arguments); }
   9343 /* This Source Code Form is subject to the terms of the Mozilla Public
   9344 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   9345 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   9346 
   9347 
   9348 
   9349 
   9350 
   9351 
   9352 
   9353 
   9354 
   9355 
   9356 
   9357 
   9358 function topSiteIconType(link) {
   9359  if (link.customScreenshotURL) {
   9360    return "custom_screenshot";
   9361  }
   9362  if (link.tippyTopIcon || link.faviconRef === "tippytop") {
   9363    return "tippytop";
   9364  }
   9365  if (link.faviconSize >= MIN_RICH_FAVICON_SIZE) {
   9366    return "rich_icon";
   9367  }
   9368  if (link.screenshot) {
   9369    return "screenshot";
   9370  }
   9371  return "no_image";
   9372 }
   9373 
   9374 /**
   9375 * Iterates through TopSites and counts types of images.
   9376 *
   9377 * @param acc Accumulator for reducer.
   9378 * @param topsite Entry in TopSites.
   9379 */
   9380 function countTopSitesIconsTypes(topSites) {
   9381  const countTopSitesTypes = (acc, link) => {
   9382    acc[topSiteIconType(link)]++;
   9383    return acc;
   9384  };
   9385  return topSites.reduce(countTopSitesTypes, {
   9386    custom_screenshot: 0,
   9387    screenshot: 0,
   9388    tippytop: 0,
   9389    rich_icon: 0,
   9390    no_image: 0
   9391  });
   9392 }
   9393 class _TopSites extends (external_React_default()).PureComponent {
   9394  constructor(props) {
   9395    super(props);
   9396    this.onEditFormClose = this.onEditFormClose.bind(this);
   9397    this.onSearchShortcutsFormClose = this.onSearchShortcutsFormClose.bind(this);
   9398  }
   9399 
   9400  /**
   9401   * Dispatch session statistics about the quality of TopSites icons and pinned count.
   9402   */
   9403  _dispatchTopSitesStats() {
   9404    const topSites = this._getVisibleTopSites().filter(topSite => topSite !== null && topSite !== undefined);
   9405    const topSitesIconsStats = countTopSitesIconsTypes(topSites);
   9406    const topSitesPinned = topSites.filter(site => !!site.isPinned).length;
   9407    const searchShortcuts = topSites.filter(site => !!site.searchTopSite).length;
   9408    // Dispatch telemetry event with the count of TopSites images types.
   9409    this.props.dispatch(actionCreators.AlsoToMain({
   9410      type: actionTypes.SAVE_SESSION_PERF_DATA,
   9411      data: {
   9412        topsites_icon_stats: topSitesIconsStats,
   9413        topsites_pinned: topSitesPinned,
   9414        topsites_search_shortcuts: searchShortcuts
   9415      }
   9416    }));
   9417  }
   9418 
   9419  /**
   9420   * Return the TopSites that are visible based on prefs and window width.
   9421   */
   9422  _getVisibleTopSites() {
   9423    // We hide 2 sites per row when not in the wide layout.
   9424    let sitesPerRow = TOP_SITES_MAX_SITES_PER_ROW;
   9425    // $break-point-widest = 1072px (from _variables.scss)
   9426    if (!globalThis.matchMedia(`(min-width: 1072px)`).matches) {
   9427      sitesPerRow -= 2;
   9428    }
   9429    return this.props.TopSites.rows.slice(0, this.props.TopSitesRows * sitesPerRow);
   9430  }
   9431  componentDidUpdate() {
   9432    this._dispatchTopSitesStats();
   9433  }
   9434  componentDidMount() {
   9435    this._dispatchTopSitesStats();
   9436  }
   9437  onEditFormClose() {
   9438    this.props.dispatch(actionCreators.UserEvent({
   9439      source: TOP_SITES_SOURCE,
   9440      event: "TOP_SITES_EDIT_CLOSE"
   9441    }));
   9442    this.props.dispatch({
   9443      type: actionTypes.TOP_SITES_CANCEL_EDIT
   9444    });
   9445  }
   9446  onSearchShortcutsFormClose() {
   9447    this.props.dispatch(actionCreators.UserEvent({
   9448      source: TOP_SITES_SOURCE,
   9449      event: "SEARCH_EDIT_CLOSE"
   9450    }));
   9451    this.props.dispatch({
   9452      type: actionTypes.TOP_SITES_CLOSE_SEARCH_SHORTCUTS_MODAL
   9453    });
   9454  }
   9455  render() {
   9456    const {
   9457      props
   9458    } = this;
   9459    const {
   9460      editForm,
   9461      showSearchShortcutsForm
   9462    } = props.TopSites;
   9463    const extraMenuOptions = ["AddTopSite"];
   9464    let visibleTopSites;
   9465    const colors = props.Prefs.values["newNewtabExperience.colors"];
   9466 
   9467    // do not run this function when for startup cache
   9468    if (!props.App.isForStartupCache.TopSites) {
   9469      visibleTopSites = this._getVisibleTopSites()?.length;
   9470    }
   9471    if (props.Prefs.values["improvesearch.topSiteSearchShortcuts"]) {
   9472      extraMenuOptions.push("AddSearchShortcut");
   9473    }
   9474    return /*#__PURE__*/external_React_default().createElement(ComponentPerfTimer, {
   9475      id: "topsites",
   9476      initialized: props.TopSites.initialized,
   9477      dispatch: props.dispatch
   9478    }, /*#__PURE__*/external_React_default().createElement(CollapsibleSection, {
   9479      className: "top-sites",
   9480      id: "topsites",
   9481      title: props.title || {
   9482        id: "newtab-section-header-topsites"
   9483      },
   9484      hideTitle: true,
   9485      extraMenuOptions: extraMenuOptions,
   9486      showPrefName: "feeds.topsites",
   9487      eventSource: TOP_SITES_SOURCE,
   9488      collapsed: false,
   9489      isFixed: props.isFixed,
   9490      isFirst: props.isFirst,
   9491      isLast: props.isLast,
   9492      dispatch: props.dispatch
   9493    }, /*#__PURE__*/external_React_default().createElement(TopSiteList, {
   9494      TopSites: props.TopSites,
   9495      TopSitesRows: props.TopSitesRows,
   9496      dispatch: props.dispatch,
   9497      topSiteIconType: topSiteIconType,
   9498      colors: colors,
   9499      visibleTopSites: visibleTopSites
   9500    }), /*#__PURE__*/external_React_default().createElement("div", {
   9501      className: "edit-topsites-wrapper"
   9502    }, editForm && /*#__PURE__*/external_React_default().createElement("div", {
   9503      className: "edit-topsites"
   9504    }, /*#__PURE__*/external_React_default().createElement(ModalOverlayWrapper, {
   9505      unstyled: true,
   9506      onClose: this.onEditFormClose,
   9507      innerClassName: "modal"
   9508    }, /*#__PURE__*/external_React_default().createElement(TopSiteForm, TopSites_extends({
   9509      site: props.TopSites.rows[editForm.index],
   9510      onClose: this.onEditFormClose,
   9511      dispatch: this.props.dispatch
   9512    }, editForm)))), showSearchShortcutsForm && /*#__PURE__*/external_React_default().createElement("div", {
   9513      className: "edit-search-shortcuts"
   9514    }, /*#__PURE__*/external_React_default().createElement(ModalOverlayWrapper, {
   9515      unstyled: true,
   9516      onClose: this.onSearchShortcutsFormClose,
   9517      innerClassName: "modal"
   9518    }, /*#__PURE__*/external_React_default().createElement(SearchShortcutsForm, {
   9519      TopSites: props.TopSites,
   9520      onClose: this.onSearchShortcutsFormClose,
   9521      dispatch: this.props.dispatch
   9522    }))))));
   9523  }
   9524 }
   9525 const TopSites_TopSites = (0,external_ReactRedux_namespaceObject.connect)(state => ({
   9526  App: state.App,
   9527  TopSites: state.TopSites,
   9528  Prefs: state.Prefs,
   9529  TopSitesRows: state.Prefs.values.topSitesRows
   9530 }))(_TopSites);
   9531 ;// CONCATENATED MODULE: ./content-src/components/Sections/Sections.jsx
   9532 function Sections_extends() { return Sections_extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, Sections_extends.apply(null, arguments); }
   9533 /* This Source Code Form is subject to the terms of the Mozilla Public
   9534 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   9535 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   9536 
   9537 
   9538 
   9539 
   9540 
   9541 
   9542 
   9543 
   9544 
   9545 
   9546 const Sections_VISIBLE = "visible";
   9547 const Sections_VISIBILITY_CHANGE_EVENT = "visibilitychange";
   9548 const CARDS_PER_ROW_DEFAULT = 3;
   9549 const CARDS_PER_ROW_COMPACT_WIDE = 4;
   9550 class Section extends (external_React_default()).PureComponent {
   9551  get numRows() {
   9552    const {
   9553      rowsPref,
   9554      maxRows,
   9555      Prefs
   9556    } = this.props;
   9557    return rowsPref ? Prefs.values[rowsPref] : maxRows;
   9558  }
   9559  _dispatchImpressionStats() {
   9560    const {
   9561      props
   9562    } = this;
   9563    let cardsPerRow = CARDS_PER_ROW_DEFAULT;
   9564    if (props.compactCards && globalThis.matchMedia(`(min-width: 1072px)`).matches) {
   9565      // If the section has compact cards and the viewport is wide enough, we show
   9566      // 4 columns instead of 3.
   9567      // $break-point-widest = 1072px (from _variables.scss)
   9568      cardsPerRow = CARDS_PER_ROW_COMPACT_WIDE;
   9569    }
   9570    const maxCards = cardsPerRow * this.numRows;
   9571    const cards = props.rows.slice(0, maxCards);
   9572    if (this.needsImpressionStats(cards)) {
   9573      props.dispatch(actionCreators.ImpressionStats({
   9574        source: props.eventSource,
   9575        tiles: cards.map(link => ({
   9576          id: link.guid
   9577        }))
   9578      }));
   9579      this.impressionCardGuids = cards.map(link => link.guid);
   9580    }
   9581  }
   9582 
   9583  // This sends an event when a user sees a set of new content. If content
   9584  // changes while the page is hidden (i.e. preloaded or on a hidden tab),
   9585  // only send the event if the page becomes visible again.
   9586  sendImpressionStatsOrAddListener() {
   9587    const {
   9588      props
   9589    } = this;
   9590    if (!props.shouldSendImpressionStats || !props.dispatch) {
   9591      return;
   9592    }
   9593    if (props.document.visibilityState === Sections_VISIBLE) {
   9594      this._dispatchImpressionStats();
   9595    } else {
   9596      // We should only ever send the latest impression stats ping, so remove any
   9597      // older listeners.
   9598      if (this._onVisibilityChange) {
   9599        props.document.removeEventListener(Sections_VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
   9600      }
   9601 
   9602      // When the page becomes visible, send the impression stats ping if the section isn't collapsed.
   9603      this._onVisibilityChange = () => {
   9604        if (props.document.visibilityState === Sections_VISIBLE) {
   9605          if (!this.props.pref.collapsed) {
   9606            this._dispatchImpressionStats();
   9607          }
   9608          props.document.removeEventListener(Sections_VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
   9609        }
   9610      };
   9611      props.document.addEventListener(Sections_VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
   9612    }
   9613  }
   9614  componentWillMount() {
   9615    this.sendNewTabRehydrated(this.props.initialized);
   9616  }
   9617  componentDidMount() {
   9618    if (this.props.rows.length && !this.props.pref.collapsed) {
   9619      this.sendImpressionStatsOrAddListener();
   9620    }
   9621  }
   9622  componentDidUpdate(prevProps) {
   9623    const {
   9624      props
   9625    } = this;
   9626    const isCollapsed = props.pref.collapsed;
   9627    const wasCollapsed = prevProps.pref.collapsed;
   9628    if (
   9629    // Don't send impression stats for the empty state
   9630    props.rows.length && (
   9631    // We only want to send impression stats if the content of the cards has changed
   9632    // and the section is not collapsed...
   9633    props.rows !== prevProps.rows && !isCollapsed ||
   9634    // or if we are expanding a section that was collapsed.
   9635    wasCollapsed && !isCollapsed)) {
   9636      this.sendImpressionStatsOrAddListener();
   9637    }
   9638  }
   9639  componentWillUpdate(nextProps) {
   9640    this.sendNewTabRehydrated(nextProps.initialized);
   9641  }
   9642  componentWillUnmount() {
   9643    if (this._onVisibilityChange) {
   9644      this.props.document.removeEventListener(Sections_VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
   9645    }
   9646  }
   9647  needsImpressionStats(cards) {
   9648    if (!this.impressionCardGuids || this.impressionCardGuids.length !== cards.length) {
   9649      return true;
   9650    }
   9651    for (let i = 0; i < cards.length; i++) {
   9652      if (cards[i].guid !== this.impressionCardGuids[i]) {
   9653        return true;
   9654      }
   9655    }
   9656    return false;
   9657  }
   9658 
   9659  // The NEW_TAB_REHYDRATED event is used to inform feeds that their
   9660  // data has been consumed e.g. for counting the number of tabs that
   9661  // have rendered that data.
   9662  sendNewTabRehydrated(initialized) {
   9663    if (initialized && !this.renderNotified) {
   9664      this.props.dispatch(actionCreators.AlsoToMain({
   9665        type: actionTypes.NEW_TAB_REHYDRATED,
   9666        data: {}
   9667      }));
   9668      this.renderNotified = true;
   9669    }
   9670  }
   9671  render() {
   9672    const {
   9673      id,
   9674      eventSource,
   9675      title,
   9676      rows,
   9677      emptyState,
   9678      dispatch,
   9679      compactCards,
   9680      read_more_endpoint,
   9681      contextMenuOptions,
   9682      initialized,
   9683      learnMore,
   9684      pref,
   9685      privacyNoticeURL,
   9686      isFirst,
   9687      isLast
   9688    } = this.props;
   9689    const waitingForSpoc = id === "topstories" && this.props.Pocket.waitingForSpoc;
   9690    const maxCardsPerRow = compactCards ? CARDS_PER_ROW_COMPACT_WIDE : CARDS_PER_ROW_DEFAULT;
   9691    const {
   9692      numRows
   9693    } = this;
   9694    const maxCards = maxCardsPerRow * numRows;
   9695    const maxCardsOnNarrow = CARDS_PER_ROW_DEFAULT * numRows;
   9696    const shouldShowReadMore = read_more_endpoint;
   9697    const realRows = rows.slice(0, maxCards);
   9698 
   9699    // The empty state should only be shown after we have initialized and there is no content.
   9700    // Otherwise, we should show placeholders.
   9701    const shouldShowEmptyState = initialized && !rows.length;
   9702    const cards = [];
   9703    if (!shouldShowEmptyState) {
   9704      for (let i = 0; i < maxCards; i++) {
   9705        const link = realRows[i];
   9706        // On narrow viewports, we only show 3 cards per row. We'll mark the rest as
   9707        // .hide-for-narrow to hide in CSS via @media query.
   9708        const className = i >= maxCardsOnNarrow ? "hide-for-narrow" : "";
   9709        let usePlaceholder = !link;
   9710        // If we are in the third card and waiting for spoc,
   9711        // use the placeholder.
   9712        if (!usePlaceholder && i === 2 && waitingForSpoc) {
   9713          usePlaceholder = true;
   9714        }
   9715        cards.push(!usePlaceholder ? /*#__PURE__*/external_React_default().createElement(Card, {
   9716          key: i,
   9717          index: i,
   9718          className: className,
   9719          dispatch: dispatch,
   9720          link: link,
   9721          contextMenuOptions: contextMenuOptions,
   9722          eventSource: eventSource,
   9723          shouldSendImpressionStats: this.props.shouldSendImpressionStats,
   9724          isWebExtension: this.props.isWebExtension
   9725        }) : /*#__PURE__*/external_React_default().createElement(PlaceholderCard, {
   9726          key: i,
   9727          className: className
   9728        }));
   9729      }
   9730    }
   9731    const sectionClassName = ["section", compactCards ? "compact-cards" : "normal-cards"].join(" ");
   9732 
   9733    // <Section> <-- React component
   9734    // <section> <-- HTML5 element
   9735    return /*#__PURE__*/external_React_default().createElement(ComponentPerfTimer, this.props, /*#__PURE__*/external_React_default().createElement(CollapsibleSection, {
   9736      className: sectionClassName,
   9737      title: title,
   9738      id: id,
   9739      eventSource: eventSource,
   9740      collapsed: this.props.pref.collapsed,
   9741      showPrefName: pref && pref.feed || id,
   9742      privacyNoticeURL: privacyNoticeURL,
   9743      Prefs: this.props.Prefs,
   9744      isFixed: this.props.isFixed,
   9745      isFirst: isFirst,
   9746      isLast: isLast,
   9747      learnMore: learnMore,
   9748      dispatch: this.props.dispatch,
   9749      isWebExtension: this.props.isWebExtension
   9750    }, !shouldShowEmptyState && /*#__PURE__*/external_React_default().createElement("ul", {
   9751      className: "section-list",
   9752      style: {
   9753        padding: 0
   9754      }
   9755    }, cards), shouldShowEmptyState && /*#__PURE__*/external_React_default().createElement("div", {
   9756      className: "section-empty-state"
   9757    }, /*#__PURE__*/external_React_default().createElement("div", {
   9758      className: "empty-state"
   9759    }, /*#__PURE__*/external_React_default().createElement(FluentOrText, {
   9760      message: emptyState.message
   9761    }, /*#__PURE__*/external_React_default().createElement("p", {
   9762      className: "empty-state-message"
   9763    })))), id === "topstories" && /*#__PURE__*/external_React_default().createElement("div", {
   9764      className: "top-stories-bottom-container"
   9765    }, /*#__PURE__*/external_React_default().createElement("div", {
   9766      className: "wrapper-more-recommendations"
   9767    }, shouldShowReadMore && /*#__PURE__*/external_React_default().createElement(MoreRecommendations, {
   9768      read_more_endpoint: read_more_endpoint
   9769    })))));
   9770  }
   9771 }
   9772 Section.defaultProps = {
   9773  document: globalThis.document,
   9774  rows: [],
   9775  emptyState: {},
   9776  pref: {},
   9777  title: ""
   9778 };
   9779 const SectionIntl = (0,external_ReactRedux_namespaceObject.connect)(state => ({
   9780  Prefs: state.Prefs,
   9781  Pocket: state.Pocket
   9782 }))(Section);
   9783 class _Sections extends (external_React_default()).PureComponent {
   9784  renderSections() {
   9785    const sections = [];
   9786    const enabledSections = this.props.Sections.filter(section => section.enabled);
   9787    const {
   9788      sectionOrder,
   9789      "feeds.topsites": showTopSites
   9790    } = this.props.Prefs.values;
   9791    // Enabled sections doesn't include Top Sites, so we add it if enabled.
   9792    const expectedCount = enabledSections.length + ~~showTopSites;
   9793    for (const sectionId of sectionOrder.split(",")) {
   9794      const commonProps = {
   9795        key: sectionId,
   9796        isFirst: sections.length === 0,
   9797        isLast: sections.length === expectedCount - 1
   9798      };
   9799      if (sectionId === "topsites" && showTopSites) {
   9800        sections.push(/*#__PURE__*/external_React_default().createElement(TopSites_TopSites, commonProps));
   9801      } else {
   9802        const section = enabledSections.find(s => s.id === sectionId);
   9803        if (section) {
   9804          sections.push(/*#__PURE__*/external_React_default().createElement(SectionIntl, Sections_extends({}, section, commonProps)));
   9805        }
   9806      }
   9807    }
   9808    return sections;
   9809  }
   9810  render() {
   9811    return /*#__PURE__*/external_React_default().createElement("div", {
   9812      className: "sections-list"
   9813    }, this.renderSections());
   9814  }
   9815 }
   9816 const Sections_Sections = (0,external_ReactRedux_namespaceObject.connect)(state => ({
   9817  Sections: state.Sections,
   9818  Prefs: state.Prefs
   9819 }))(_Sections);
   9820 ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/Highlights/Highlights.jsx
   9821 function Highlights_extends() { return Highlights_extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, Highlights_extends.apply(null, arguments); }
   9822 /* This Source Code Form is subject to the terms of the Mozilla Public
   9823 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   9824 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   9825 
   9826 
   9827 
   9828 
   9829 class _Highlights extends (external_React_default()).PureComponent {
   9830  render() {
   9831    const section = this.props.Sections.find(s => s.id === "highlights");
   9832    if (!section || !section.enabled) {
   9833      return null;
   9834    }
   9835    return /*#__PURE__*/external_React_default().createElement("div", {
   9836      className: "ds-highlights sections-list"
   9837    }, /*#__PURE__*/external_React_default().createElement(SectionIntl, Highlights_extends({}, section, {
   9838      isFixed: true
   9839    })));
   9840  }
   9841 }
   9842 const Highlights = (0,external_ReactRedux_namespaceObject.connect)(state => ({
   9843  Sections: state.Sections
   9844 }))(_Highlights);
   9845 ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/HorizontalRule/HorizontalRule.jsx
   9846 /* This Source Code Form is subject to the terms of the Mozilla Public
   9847 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   9848 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   9849 
   9850 
   9851 class HorizontalRule extends (external_React_default()).PureComponent {
   9852  render() {
   9853    return /*#__PURE__*/external_React_default().createElement("hr", {
   9854      className: "ds-hr"
   9855    });
   9856  }
   9857 }
   9858 ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/Navigation/Navigation.jsx
   9859 /* This Source Code Form is subject to the terms of the Mozilla Public
   9860 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   9861 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   9862 
   9863 
   9864 
   9865 
   9866 
   9867 class Topic extends (external_React_default()).PureComponent {
   9868  constructor(props) {
   9869    super(props);
   9870    this.onLinkClick = this.onLinkClick.bind(this);
   9871  }
   9872  onLinkClick(event) {
   9873    if (this.props.dispatch) {
   9874      this.props.dispatch(actionCreators.DiscoveryStreamUserEvent({
   9875        event: "CLICK",
   9876        source: "POPULAR_TOPICS",
   9877        action_position: 0,
   9878        value: {
   9879          topic: event.target.text.toLowerCase().replace(` `, `-`)
   9880        }
   9881      }));
   9882    }
   9883  }
   9884  render() {
   9885    const {
   9886      url,
   9887      name: topicName
   9888    } = this.props;
   9889    return /*#__PURE__*/external_React_default().createElement(SafeAnchor, {
   9890      onLinkClick: this.onLinkClick,
   9891      className: this.props.className,
   9892      url: url
   9893    }, topicName);
   9894  }
   9895 }
   9896 class Navigation extends (external_React_default()).PureComponent {
   9897  render() {
   9898    let links = this.props.links || [];
   9899    const alignment = this.props.alignment || "centered";
   9900    const header = this.props.header || {};
   9901    const english = this.props.locale.startsWith("en-");
   9902    const privacyNotice = this.props.privacyNoticeURL || {};
   9903    const {
   9904      newFooterSection
   9905    } = this.props;
   9906    const className = `ds-navigation ds-navigation-${alignment} ${newFooterSection ? `ds-navigation-new-topics` : ``}`;
   9907    let {
   9908      title
   9909    } = header;
   9910    if (newFooterSection) {
   9911      title = {
   9912        id: "newtab-pocket-new-topics-title"
   9913      };
   9914      if (this.props.extraLinks) {
   9915        links = [...links.slice(0, links.length - 1), ...this.props.extraLinks, links[links.length - 1]];
   9916      }
   9917    }
   9918    return /*#__PURE__*/external_React_default().createElement("div", {
   9919      className: className
   9920    }, title && english ? /*#__PURE__*/external_React_default().createElement(FluentOrText, {
   9921      message: title
   9922    }, /*#__PURE__*/external_React_default().createElement("span", {
   9923      className: "ds-navigation-header"
   9924    })) : null, english ? /*#__PURE__*/external_React_default().createElement("ul", null, links && links.map(t => /*#__PURE__*/external_React_default().createElement("li", {
   9925      key: t.name
   9926    }, /*#__PURE__*/external_React_default().createElement(Topic, {
   9927      url: t.url,
   9928      name: t.name,
   9929      dispatch: this.props.dispatch
   9930    })))) : null, !newFooterSection ? /*#__PURE__*/external_React_default().createElement(SafeAnchor, {
   9931      className: "ds-navigation-privacy",
   9932      url: privacyNotice.url
   9933    }, /*#__PURE__*/external_React_default().createElement(FluentOrText, {
   9934      message: privacyNotice.title
   9935    })) : null, newFooterSection ? /*#__PURE__*/external_React_default().createElement("div", {
   9936      className: "ds-navigation-family"
   9937    }, /*#__PURE__*/external_React_default().createElement("span", {
   9938      className: "icon firefox-logo"
   9939    }), /*#__PURE__*/external_React_default().createElement("span", null, "|"), /*#__PURE__*/external_React_default().createElement("span", {
   9940      className: "icon pocket-logo"
   9941    }), /*#__PURE__*/external_React_default().createElement("span", {
   9942      className: "ds-navigation-family-message",
   9943      "data-l10n-id": "newtab-pocket-pocket-firefox-family"
   9944    })) : null);
   9945  }
   9946 }
   9947 ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/PrivacyLink/PrivacyLink.jsx
   9948 /* This Source Code Form is subject to the terms of the Mozilla Public
   9949 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   9950 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   9951 
   9952 
   9953 
   9954 
   9955 class PrivacyLink extends (external_React_default()).PureComponent {
   9956  render() {
   9957    const {
   9958      properties
   9959    } = this.props;
   9960    return /*#__PURE__*/external_React_default().createElement("div", {
   9961      className: "ds-privacy-link"
   9962    }, /*#__PURE__*/external_React_default().createElement(SafeAnchor, {
   9963      url: properties.url
   9964    }, /*#__PURE__*/external_React_default().createElement(FluentOrText, {
   9965      message: properties.title
   9966    })));
   9967  }
   9968 }
   9969 ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/SectionTitle/SectionTitle.jsx
   9970 /* This Source Code Form is subject to the terms of the Mozilla Public
   9971 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   9972 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   9973 
   9974 
   9975 class SectionTitle extends (external_React_default()).PureComponent {
   9976  render() {
   9977    const {
   9978      header: {
   9979        title,
   9980        subtitle
   9981      }
   9982    } = this.props;
   9983    return /*#__PURE__*/external_React_default().createElement("div", {
   9984      className: "ds-section-title"
   9985    }, /*#__PURE__*/external_React_default().createElement("div", {
   9986      className: "title"
   9987    }, title), subtitle ? /*#__PURE__*/external_React_default().createElement("div", {
   9988      className: "subtitle"
   9989    }, subtitle) : null);
   9990  }
   9991 }
   9992 ;// CONCATENATED MODULE: ./content-src/lib/selectLayoutRender.mjs
   9993 /* This Source Code Form is subject to the terms of the Mozilla Public
   9994 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
   9995 * You can obtain one at http://mozilla.org/MPL/2.0/. */
   9996 
   9997 const selectLayoutRender = ({ state = {}, prefs = {} }) => {
   9998  const { layout, feeds, spocs } = state;
   9999  let spocIndexPlacementMap = {};
  10000 
  10001  /* This function fills spoc positions on a per placement basis with available spocs.
  10002   * It does this by looping through each position for a placement and replacing a rec with a spoc.
  10003   * If it runs out of spocs or positions, it stops.
  10004   * If it sees the same placement again, it remembers the previous spoc index, and continues.
  10005   * If it sees a blocked spoc, it skips that position leaving in a regular story.
  10006   */
  10007  function fillSpocPositionsForPlacement(
  10008    data,
  10009    spocsPositions,
  10010    spocsData,
  10011    placementName
  10012  ) {
  10013    if (
  10014      !spocIndexPlacementMap[placementName] &&
  10015      spocIndexPlacementMap[placementName] !== 0
  10016    ) {
  10017      spocIndexPlacementMap[placementName] = 0;
  10018    }
  10019    const results = [...data];
  10020    for (let position of spocsPositions) {
  10021      const spoc = spocsData[spocIndexPlacementMap[placementName]];
  10022      // If there are no spocs left, we can stop filling positions.
  10023      if (!spoc) {
  10024        break;
  10025      }
  10026 
  10027      // A placement could be used in two sections.
  10028      // In these cases, we want to maintain the index of the previous section.
  10029      // If we didn't do this, it might duplicate spocs.
  10030      spocIndexPlacementMap[placementName]++;
  10031 
  10032      // A spoc that's blocked is removed from the source for subsequent newtab loads.
  10033      // If we have a spoc in the source that's blocked, it means it was *just* blocked,
  10034      // and in this case, we skip this position, and show a regular spoc instead.
  10035      if (!spocs.blocked.includes(spoc.url)) {
  10036        results.splice(position.index, 0, spoc);
  10037      }
  10038    }
  10039 
  10040    return results;
  10041  }
  10042 
  10043  const positions = {};
  10044  const DS_COMPONENTS = [
  10045    "Message",
  10046    "SectionTitle",
  10047    "Navigation",
  10048    "Widgets",
  10049    "CardGrid",
  10050    "HorizontalRule",
  10051    "PrivacyLink",
  10052  ];
  10053 
  10054  const filterArray = [];
  10055 
  10056  // Filter sections is Topsites are turned off
  10057  if (!prefs["feeds.topsites"]) {
  10058    filterArray.push("TopSites");
  10059  }
  10060 
  10061  // Filter sections is Widgets are turned off
  10062  // Note extra logic is required bc this feature can be enabled via Nimbus
  10063  const nimbusWidgetsTrainhopEnabled = prefs.trainhopConfig?.widgets?.enabled;
  10064  const nimbusWidgetsEnabled = prefs.widgetsConfig?.enabled;
  10065  const widgetsEnabled = prefs["widgets.system.enabled"];
  10066  if (
  10067    !nimbusWidgetsTrainhopEnabled &&
  10068    !nimbusWidgetsEnabled &&
  10069    !widgetsEnabled
  10070  ) {
  10071    filterArray.push("Widgets");
  10072  }
  10073 
  10074  // Filter sections is Recommended Stories are turned off
  10075  const pocketEnabled =
  10076    prefs["feeds.section.topstories"] && prefs["feeds.system.topstories"];
  10077  if (!pocketEnabled) {
  10078    filterArray.push(
  10079      // Bug 1980459 - Do not remove Widgets if DS is disabled
  10080      ...DS_COMPONENTS.filter(component => component !== "Widgets")
  10081    );
  10082  }
  10083 
  10084  // function to determine amount of tiles shown per section per viewport
  10085  function getMaxTiles(responsiveLayouts) {
  10086    return responsiveLayouts
  10087      .flatMap(responsiveLayout => responsiveLayout)
  10088      .reduce((acc, t) => {
  10089        acc[t.columnCount] = t.tiles.length;
  10090 
  10091        // Update maxTile if current tile count is greater
  10092        if (!acc.maxTile || t.tiles.length > acc.maxTile) {
  10093          acc.maxTile = t.tiles.length;
  10094        }
  10095        return acc;
  10096      }, {});
  10097  }
  10098 
  10099  const placeholderComponent = component => {
  10100    if (!component.feed) {
  10101      // TODO we now need a placeholder for topsites.
  10102      return {
  10103        ...component,
  10104        data: {
  10105          spocs: [],
  10106        },
  10107      };
  10108    }
  10109    const data = {
  10110      recommendations: [],
  10111      sections: [
  10112        {
  10113          layout: {
  10114            responsiveLayouts: [],
  10115          },
  10116          data: [],
  10117        },
  10118      ],
  10119    };
  10120 
  10121    let items = 0;
  10122    if (component.properties && component.properties.items) {
  10123      items = component.properties.items;
  10124    }
  10125    for (let i = 0; i < items; i++) {
  10126      data.recommendations.push({ placeholder: true });
  10127    }
  10128 
  10129    const sectionsEnabled = prefs["discoverystream.sections.enabled"];
  10130    if (sectionsEnabled) {
  10131      for (let i = 0; i < items; i++) {
  10132        data.sections[0].data.push({ placeholder: true });
  10133      }
  10134    }
  10135 
  10136    return { ...component, data };
  10137  };
  10138 
  10139  // TODO update devtools to show placements
  10140  const handleSpocs = (data = [], spocsPositions, spocsPlacement) => {
  10141    let result = [...data];
  10142    // Do we ever expect to possibly have a spoc.
  10143    if (spocsPositions?.length) {
  10144      const placement = spocsPlacement || {};
  10145      const placementName = placement.name || "newtab_spocs";
  10146      const spocsData = spocs.data[placementName];
  10147 
  10148      // We expect a spoc, spocs are loaded, and the server returned spocs.
  10149      if (spocs.loaded && spocsData?.items?.length) {
  10150        // Since banner-type ads are placed by row and don't use the normal spoc position,
  10151        // dont combine with content
  10152        const excludedSpocs = ["billboard", "leaderboard"];
  10153        const filteredSpocs = spocsData?.items?.filter(
  10154          item => !excludedSpocs.includes(item.format)
  10155        );
  10156        result = fillSpocPositionsForPlacement(
  10157          result,
  10158          spocsPositions,
  10159          filteredSpocs,
  10160          placementName
  10161        );
  10162      }
  10163    }
  10164    return result;
  10165  };
  10166 
  10167  const handleSections = (sections = [], recommendations = []) => {
  10168    let result = sections.sort((a, b) => a.receivedRank - b.receivedRank);
  10169 
  10170    const sectionsMap = recommendations.reduce((acc, recommendation) => {
  10171      const { section } = recommendation;
  10172      acc[section] = acc[section] || [];
  10173      acc[section].push(recommendation);
  10174      return acc;
  10175    }, {});
  10176 
  10177    result.forEach(section => {
  10178      const { sectionKey } = section;
  10179      section.data = sectionsMap[sectionKey];
  10180    });
  10181 
  10182    return result;
  10183  };
  10184 
  10185  const handleComponent = component => {
  10186    if (component?.spocs?.positions?.length) {
  10187      const placement = component.placement || {};
  10188      const placementName = placement.name || "newtab_spocs";
  10189      const spocsData = spocs.data[placementName];
  10190      if (spocs.loaded && spocsData?.items?.length) {
  10191        return {
  10192          ...component,
  10193          data: {
  10194            spocs: spocsData.items
  10195              .filter(spoc => spoc && !spocs.blocked.includes(spoc.url))
  10196              .map((spoc, index) => ({
  10197                ...spoc,
  10198                pos: index,
  10199              })),
  10200          },
  10201        };
  10202      }
  10203    }
  10204    return {
  10205      ...component,
  10206      data: {
  10207        spocs: [],
  10208      },
  10209    };
  10210  };
  10211 
  10212  const handleComponentWithFeed = component => {
  10213    positions[component.type] = positions[component.type] || 0;
  10214    let data = {
  10215      recommendations: [],
  10216      sections: [],
  10217    };
  10218 
  10219    const feed = feeds.data[component.feed.url];
  10220    if (feed?.data) {
  10221      data = {
  10222        ...feed.data,
  10223        recommendations: [...(feed.data.recommendations || [])],
  10224        sections: [...(feed.data.sections || [])],
  10225      };
  10226    }
  10227 
  10228    if (component && component.properties && component.properties.offset) {
  10229      data = {
  10230        ...data,
  10231        recommendations: data.recommendations.slice(
  10232          component.properties.offset
  10233        ),
  10234      };
  10235    }
  10236    const spocsPositions = component?.spocs?.positions;
  10237    const spocsPlacement = component?.placement;
  10238 
  10239    const sectionsEnabled = prefs["discoverystream.sections.enabled"];
  10240    data = {
  10241      ...data,
  10242      ...(sectionsEnabled
  10243        ? {
  10244            sections: handleSections(data.sections, data.recommendations).map(
  10245              section => {
  10246                const sectionsSpocsPositions = [];
  10247                section.layout.responsiveLayouts
  10248                  // Initial position for spocs is going to be for the smallest breakpoint.
  10249                  // We can then move it from there via breakpoints.
  10250                  .find(item => item.columnCount === 1)
  10251                  .tiles.forEach(tile => {
  10252                    if (tile.hasAd) {
  10253                      sectionsSpocsPositions.push({ index: tile.position });
  10254                    }
  10255                  });
  10256                return {
  10257                  ...section,
  10258                  data: handleSpocs(
  10259                    section.data,
  10260                    sectionsSpocsPositions,
  10261                    spocsPlacement
  10262                  ),
  10263                };
  10264              }
  10265            ),
  10266            // We don't fill spocs in recs if sections are enabled,
  10267            // because recs are not going to be seen.
  10268            recommendations: data.recommendations,
  10269          }
  10270        : {
  10271            recommendations: handleSpocs(
  10272              data.recommendations,
  10273              spocsPositions,
  10274              spocsPlacement
  10275            ),
  10276          }),
  10277    };
  10278 
  10279    let items = 0;
  10280    if (component.properties && component.properties.items) {
  10281      items = Math.min(component.properties.items, data.recommendations.length);
  10282    }
  10283 
  10284    // loop through a component items
  10285    // Store the items position sequentially for multiple components of the same type.
  10286    // Example: A second card grid starts pos offset from the last card grid.
  10287    for (let i = 0; i < items; i++) {
  10288      data.recommendations[i] = {
  10289        ...data.recommendations[i],
  10290        pos: positions[component.type]++,
  10291      };
  10292    }
  10293 
  10294    // Setup absolute positions for sections layout.
  10295    if (sectionsEnabled) {
  10296      let currentPosition = 0;
  10297      data.sections.forEach(section => {
  10298        // We assume the count for the breakpoint with the most tiles.
  10299        const { maxTile } = getMaxTiles(section?.layout?.responsiveLayouts);
  10300        for (let i = 0; i < maxTile; i++) {
  10301          if (section.data[i]) {
  10302            section.data[i] = {
  10303              ...section.data[i],
  10304              pos: currentPosition++,
  10305            };
  10306          }
  10307        }
  10308      });
  10309    }
  10310 
  10311    return { ...component, data };
  10312  };
  10313 
  10314  const renderLayout = () => {
  10315    const renderedLayoutArray = [];
  10316    for (const row of layout.filter(
  10317      r => r.components.filter(c => !filterArray.includes(c.type)).length
  10318    )) {
  10319      let components = [];
  10320      renderedLayoutArray.push({
  10321        ...row,
  10322        components,
  10323      });
  10324      for (const component of row.components.filter(
  10325        c => !filterArray.includes(c.type)
  10326      )) {
  10327        const spocsConfig = component.spocs;
  10328        if (spocsConfig || component.feed) {
  10329          if (
  10330            (component.feed && !feeds.data[component.feed.url]) ||
  10331            (spocsConfig &&
  10332              spocsConfig.positions &&
  10333              spocsConfig.positions.length &&
  10334              !spocs.loaded)
  10335          ) {
  10336            components.push(placeholderComponent(component));
  10337          } else if (component.feed) {
  10338            components.push(handleComponentWithFeed(component));
  10339          } else {
  10340            components.push(handleComponent(component));
  10341          }
  10342        } else {
  10343          components.push(component);
  10344        }
  10345      }
  10346    }
  10347    return renderedLayoutArray;
  10348  };
  10349 
  10350  const layoutRender = renderLayout();
  10351 
  10352  return { layoutRender };
  10353 };
  10354 
  10355 ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/SectionContextMenu/SectionContextMenu.jsx
  10356 /* This Source Code Form is subject to the terms of the Mozilla Public
  10357 * License, v. 2.0. If a copy of the MPL was not distributed with this
  10358 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
  10359 
  10360 
  10361 
  10362 
  10363 /**
  10364 * A context menu for blocking, following and unfollowing sections.
  10365 *
  10366 * @param props
  10367 * @returns {React.FunctionComponent}
  10368 */
  10369 function SectionContextMenu({
  10370  type = "DISCOVERY_STREAM",
  10371  title,
  10372  source,
  10373  index,
  10374  dispatch,
  10375  sectionKey,
  10376  following,
  10377  sectionPersonalization,
  10378  sectionPosition
  10379 }) {
  10380  // Initial context menu options: block this section only.
  10381  const SECTIONS_CONTEXT_MENU_OPTIONS = ["SectionBlock"];
  10382  const [showContextMenu, setShowContextMenu] = (0,external_React_namespaceObject.useState)(false);
  10383  if (following) {
  10384    SECTIONS_CONTEXT_MENU_OPTIONS.push("SectionUnfollow");
  10385  }
  10386  const onClick = e => {
  10387    e.preventDefault();
  10388    setShowContextMenu(!showContextMenu);
  10389  };
  10390  const onUpdate = () => {
  10391    setShowContextMenu(!showContextMenu);
  10392  };
  10393  return /*#__PURE__*/external_React_default().createElement("div", {
  10394    className: "section-context-menu"
  10395  }, /*#__PURE__*/external_React_default().createElement("moz-button", {
  10396    type: "icon",
  10397    size: "default",
  10398    iconsrc: "chrome://global/skin/icons/more.svg",
  10399    title: title || source,
  10400    onClick: onClick
  10401  }), showContextMenu && /*#__PURE__*/external_React_default().createElement(LinkMenu, {
  10402    onUpdate: onUpdate,
  10403    dispatch: dispatch,
  10404    index: index,
  10405    source: type.toUpperCase(),
  10406    options: SECTIONS_CONTEXT_MENU_OPTIONS,
  10407    shouldSendImpressionStats: true,
  10408    site: {
  10409      sectionPersonalization,
  10410      sectionKey,
  10411      sectionPosition,
  10412      title
  10413    }
  10414  }));
  10415 }
  10416 ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/InterestPicker/InterestPicker.jsx
  10417 /* This Source Code Form is subject to the terms of the Mozilla Public
  10418 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  10419 * You can obtain one at http://mozilla.org/MPL/2.0/. */
  10420 
  10421 
  10422 
  10423 
  10424 
  10425 const PREF_VISIBLE_SECTIONS = "discoverystream.sections.interestPicker.visibleSections";
  10426 
  10427 /**
  10428 * Shows a list of recommended topics with visual indication whether
  10429 * the user follows some of the topics (active, blue, selected topics)
  10430 * or is yet to do so (neutrally-coloured topics with a "plus" button).
  10431 *
  10432 * @returns {React.Element}
  10433 */
  10434 function InterestPicker({
  10435  title,
  10436  subtitle,
  10437  interests,
  10438  receivedFeedRank
  10439 }) {
  10440  const dispatch = (0,external_ReactRedux_namespaceObject.useDispatch)();
  10441  const focusedRef = (0,external_React_namespaceObject.useRef)(null);
  10442  const focusRef = (0,external_React_namespaceObject.useRef)(null);
  10443  const [focusedIndex, setFocusedIndex] = (0,external_React_namespaceObject.useState)(0);
  10444  const prefs = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.Prefs.values);
  10445  const {
  10446    sectionPersonalization
  10447  } = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.DiscoveryStream);
  10448  const visibleSections = prefs[PREF_VISIBLE_SECTIONS]?.split(",").map(item => item.trim()).filter(item => item);
  10449  const handleIntersection = (0,external_React_namespaceObject.useCallback)(() => {
  10450    dispatch(actionCreators.AlsoToMain({
  10451      type: actionTypes.INLINE_SELECTION_IMPRESSION,
  10452      data: {
  10453        section_position: receivedFeedRank
  10454      }
  10455    }));
  10456  }, [dispatch, receivedFeedRank]);
  10457  const ref = useIntersectionObserver(handleIntersection);
  10458  const onKeyDown = (0,external_React_namespaceObject.useCallback)(e => {
  10459    if (e.key === "ArrowDown" || e.key === "ArrowUp") {
  10460      // prevent the page from scrolling up/down while navigating.
  10461      e.preventDefault();
  10462    }
  10463    if (focusedRef.current?.nextSibling?.querySelector("input") && e.key === "ArrowDown") {
  10464      focusedRef.current.nextSibling.querySelector("input").tabIndex = 0;
  10465      focusedRef.current.nextSibling.querySelector("input").focus();
  10466    }
  10467    if (focusedRef.current?.previousSibling?.querySelector("input") && e.key === "ArrowUp") {
  10468      focusedRef.current.previousSibling.querySelector("input").tabIndex = 0;
  10469      focusedRef.current.previousSibling.querySelector("input").focus();
  10470    }
  10471  }, []);
  10472  function onWrapperFocus() {
  10473    focusRef.current?.addEventListener("keydown", onKeyDown);
  10474  }
  10475  function onWrapperBlur() {
  10476    focusRef.current?.removeEventListener("keydown", onKeyDown);
  10477  }
  10478  function onItemFocus(index) {
  10479    setFocusedIndex(index);
  10480  }
  10481 
  10482  // Updates user preferences as they follow or unfollow topics
  10483  // by selecting them from the list
  10484  function handleChange(e, index) {
  10485    const {
  10486      name: topic,
  10487      checked
  10488    } = e.target;
  10489    let updatedSections = {
  10490      ...sectionPersonalization
  10491    };
  10492    if (checked) {
  10493      updatedSections[topic] = {
  10494        isFollowed: true,
  10495        isBlocked: false,
  10496        followedAt: new Date().toISOString()
  10497      };
  10498      if (!visibleSections.includes(topic)) {
  10499        // add section to visible sections and place after the inline picker
  10500        // subtract 1 from the rank so that it is normalized with array index
  10501        visibleSections.splice(receivedFeedRank - 1, 0, topic);
  10502        dispatch(actionCreators.SetPref(PREF_VISIBLE_SECTIONS, visibleSections.join(", ")));
  10503      }
  10504    } else {
  10505      delete updatedSections[topic];
  10506    }
  10507    dispatch(actionCreators.OnlyToMain({
  10508      type: actionTypes.INLINE_SELECTION_CLICK,
  10509      data: {
  10510        topic,
  10511        is_followed: checked,
  10512        topic_position: index,
  10513        section_position: receivedFeedRank
  10514      }
  10515    }));
  10516    dispatch(actionCreators.AlsoToMain({
  10517      type: actionTypes.SECTION_PERSONALIZATION_SET,
  10518      data: updatedSections
  10519    }));
  10520  }
  10521  return /*#__PURE__*/external_React_default().createElement("section", {
  10522    className: "inline-selection-wrapper ds-section",
  10523    ref: el => {
  10524      ref.current = [el];
  10525    }
  10526  }, /*#__PURE__*/external_React_default().createElement("div", {
  10527    className: "section-heading"
  10528  }, /*#__PURE__*/external_React_default().createElement("div", {
  10529    className: "section-title-wrapper"
  10530  }, /*#__PURE__*/external_React_default().createElement("h2", {
  10531    className: "section-title"
  10532  }, title), /*#__PURE__*/external_React_default().createElement("p", {
  10533    className: "section-subtitle"
  10534  }, subtitle))), /*#__PURE__*/external_React_default().createElement("ul", {
  10535    className: "topic-list",
  10536    onFocus: onWrapperFocus,
  10537    onBlur: onWrapperBlur,
  10538    ref: focusRef
  10539  }, interests.map((interest, index) => {
  10540    const checked = sectionPersonalization[interest.sectionId]?.isFollowed;
  10541    return /*#__PURE__*/external_React_default().createElement("li", {
  10542      key: interest.sectionId,
  10543      ref: index === focusedIndex ? focusedRef : null
  10544    }, /*#__PURE__*/external_React_default().createElement("label", null, /*#__PURE__*/external_React_default().createElement("input", {
  10545      type: "checkbox",
  10546      id: interest.sectionId,
  10547      name: interest.sectionId,
  10548      checked: checked,
  10549      "aria-checked": checked,
  10550      onChange: e => handleChange(e, index),
  10551      key: `${interest.sectionId}-${checked}` // Force remount to sync DOM state with React state
  10552      ,
  10553      tabIndex: index === focusedIndex ? 0 : -1,
  10554      onFocus: () => {
  10555        onItemFocus(index);
  10556      }
  10557    }), /*#__PURE__*/external_React_default().createElement("span", {
  10558      className: "topic-item-label"
  10559    }, interest.title || ""), /*#__PURE__*/external_React_default().createElement("div", {
  10560      className: `topic-item-icon icon ${checked ? "icon-check-filled" : "icon-add-circle-fill"}`
  10561    })));
  10562  })), /*#__PURE__*/external_React_default().createElement("p", {
  10563    className: "learn-more-copy"
  10564  }, /*#__PURE__*/external_React_default().createElement("a", {
  10565    href: prefs["support.url"],
  10566    "data-l10n-id": "newtab-topic-selection-privacy-link"
  10567  })));
  10568 }
  10569 
  10570 ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/PersonalizedCard/PersonalizedCard.jsx
  10571 /* This Source Code Form is subject to the terms of the Mozilla Public
  10572 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  10573 * You can obtain one at http://mozilla.org/MPL/2.0/. */
  10574 
  10575 
  10576 
  10577 
  10578 const PersonalizedCard = ({
  10579  dispatch,
  10580  handleDismiss,
  10581  handleClick,
  10582  handleBlock,
  10583  messageData
  10584 }) => {
  10585  const kitFox = "chrome://newtab/content/data/content/assets/kit.png";
  10586  const onDismiss = (0,external_React_namespaceObject.useCallback)(() => {
  10587    handleDismiss();
  10588    handleBlock();
  10589  }, [handleDismiss, handleBlock]);
  10590  const onToggleClick = (0,external_React_namespaceObject.useCallback)(elementId => {
  10591    dispatch({
  10592      type: actionTypes.SHOW_PERSONALIZE
  10593    });
  10594    dispatch(actionCreators.UserEvent({
  10595      event: "SHOW_PERSONALIZE"
  10596    }));
  10597    handleClick(elementId);
  10598  }, [dispatch, handleClick]);
  10599  return /*#__PURE__*/external_React_default().createElement("aside", {
  10600    className: "personalized-card-wrapper"
  10601  }, /*#__PURE__*/external_React_default().createElement("div", {
  10602    className: "personalized-card-dismiss"
  10603  }, /*#__PURE__*/external_React_default().createElement("moz-button", {
  10604    type: "icon ghost",
  10605    iconSrc: "chrome://global/skin/icons/close.svg",
  10606    onClick: onDismiss,
  10607    "data-l10n-id": "newtab-toast-dismiss-button"
  10608  })), /*#__PURE__*/external_React_default().createElement("div", {
  10609    className: "personalized-card-inner"
  10610  }, /*#__PURE__*/external_React_default().createElement("img", {
  10611    src: kitFox,
  10612    alt: ""
  10613  }), /*#__PURE__*/external_React_default().createElement("h2", null, messageData.content.cardTitle), /*#__PURE__*/external_React_default().createElement("p", null, messageData.content.cardMessage), /*#__PURE__*/external_React_default().createElement("div", {
  10614    className: "personalized-card-cta-wrapper"
  10615  }, /*#__PURE__*/external_React_default().createElement("moz-button", {
  10616    type: "primary",
  10617    class: "personalized-card-cta",
  10618    onClick: () => onToggleClick("open-personalization-panel")
  10619  }, messageData.content.ctaText), /*#__PURE__*/external_React_default().createElement(SafeAnchor, {
  10620    className: "personalized-card-link",
  10621    dispatch: dispatch,
  10622    url: messageData.content.linkUrl || "https://support.mozilla.org/",
  10623    onLinkClick: () => {
  10624      handleClick("link-click");
  10625    }
  10626  }, messageData.content.linkText))));
  10627 };
  10628 ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/FeatureHighlight/FollowSectionButtonHighlight.jsx
  10629 /* This Source Code Form is subject to the terms of the Mozilla Public
  10630 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  10631 * You can obtain one at http://mozilla.org/MPL/2.0/. */
  10632 
  10633 
  10634 
  10635 function FollowSectionButtonHighlight({
  10636  arrowPosition,
  10637  dispatch,
  10638  feature,
  10639  handleBlock,
  10640  handleDismiss,
  10641  messageData,
  10642  position,
  10643  verticalPosition
  10644 }) {
  10645  const onDismiss = (0,external_React_namespaceObject.useCallback)(() => {
  10646    handleDismiss();
  10647    handleBlock();
  10648  }, [handleDismiss, handleBlock]);
  10649  return /*#__PURE__*/external_React_default().createElement("div", {
  10650    className: `follow-section-button-highlight ${messageData.content?.darkModeDismiss ? "is-inverted-dark-dismiss-button" : ""}`
  10651  }, /*#__PURE__*/external_React_default().createElement(FeatureHighlight, {
  10652    position: position,
  10653    arrowPosition: arrowPosition,
  10654    verticalPosition: verticalPosition,
  10655    feature: feature,
  10656    dispatch: dispatch,
  10657    message: /*#__PURE__*/external_React_default().createElement("div", {
  10658      className: "follow-section-button-highlight-content"
  10659    }, /*#__PURE__*/external_React_default().createElement("picture", {
  10660      className: "follow-section-button-highlight-image"
  10661    }, /*#__PURE__*/external_React_default().createElement("source", {
  10662      srcSet: messageData.content?.darkModeImageURL || "chrome://newtab/content/data/content/assets/highlights/omc-newtab-follow.svg",
  10663      media: "(prefers-color-scheme: dark)"
  10664    }), /*#__PURE__*/external_React_default().createElement("source", {
  10665      srcSet: messageData.content?.imageURL || "chrome://newtab/content/data/content/assets/highlights/omc-newtab-follow.svg",
  10666      media: "(prefers-color-scheme: light)"
  10667    }), /*#__PURE__*/external_React_default().createElement("img", {
  10668      width: "320",
  10669      height: "195",
  10670      alt: ""
  10671    })), /*#__PURE__*/external_React_default().createElement("div", {
  10672      className: "follow-section-button-highlight-copy"
  10673    }, messageData.content?.cardTitle ? /*#__PURE__*/external_React_default().createElement("p", {
  10674      className: "title"
  10675    }, messageData.content.cardTitle) : /*#__PURE__*/external_React_default().createElement("p", {
  10676      className: "title",
  10677      "data-l10n-id": "newtab-section-follow-highlight-title"
  10678    }), messageData.content?.cardMessage ? /*#__PURE__*/external_React_default().createElement("p", {
  10679      className: "subtitle"
  10680    }, messageData.content.cardMessage) : /*#__PURE__*/external_React_default().createElement("p", {
  10681      className: "subtitle",
  10682      "data-l10n-id": "newtab-section-follow-highlight-subtitle"
  10683    }))),
  10684    openedOverride: true,
  10685    showButtonIcon: false,
  10686    dismissCallback: onDismiss,
  10687    outsideClickCallback: handleDismiss
  10688  }));
  10689 }
  10690 ;// CONCATENATED MODULE: ./content-src/components/Weather/LocationSearch.jsx
  10691 /* This Source Code Form is subject to the terms of the Mozilla Public
  10692 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  10693 * You can obtain one at http://mozilla.org/MPL/2.0/. */
  10694 
  10695 
  10696 
  10697 
  10698 function LocationSearch({
  10699  outerClassName
  10700 }) {
  10701  // should be the location object from suggestedLocations
  10702  const [selectedLocation, setSelectedLocation] = (0,external_React_namespaceObject.useState)("");
  10703  const suggestedLocations = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.Weather.suggestedLocations);
  10704  const locationSearchString = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.Weather.locationSearchString);
  10705  const [userInput, setUserInput] = (0,external_React_namespaceObject.useState)(locationSearchString || "");
  10706  const inputRef = (0,external_React_namespaceObject.useRef)(null);
  10707  const dispatch = (0,external_ReactRedux_namespaceObject.useDispatch)();
  10708  (0,external_React_namespaceObject.useEffect)(() => {
  10709    if (selectedLocation) {
  10710      dispatch(actionCreators.AlsoToMain({
  10711        type: actionTypes.WEATHER_LOCATION_DATA_UPDATE,
  10712        data: {
  10713          city: selectedLocation.localized_name,
  10714          adminName: selectedLocation.administrative_area,
  10715          country: selectedLocation.country
  10716        }
  10717      }));
  10718      dispatch(actionCreators.SetPref("weather.query", selectedLocation.key));
  10719      dispatch(actionCreators.BroadcastToContent({
  10720        type: actionTypes.WEATHER_SEARCH_ACTIVE,
  10721        data: false
  10722      }));
  10723    }
  10724  }, [selectedLocation, dispatch]);
  10725 
  10726  // when component mounts, set focus to input
  10727  (0,external_React_namespaceObject.useEffect)(() => {
  10728    inputRef?.current?.focus();
  10729  }, [inputRef]);
  10730  function handleChange(event) {
  10731    const {
  10732      value
  10733    } = event.target;
  10734    setUserInput(value);
  10735 
  10736    // if the user input contains less than three characters and suggestedLocations is not an empty array,
  10737    // reset suggestedLocations to [] so there aren't incorrect items in the datalist
  10738    if (value.length < 3 && suggestedLocations.length) {
  10739      dispatch(actionCreators.AlsoToMain({
  10740        type: actionTypes.WEATHER_LOCATION_SUGGESTIONS_UPDATE,
  10741        data: []
  10742      }));
  10743    }
  10744    // find match in suggestedLocation array
  10745    const match = suggestedLocations?.find(({
  10746      key
  10747    }) => key === value);
  10748    if (match) {
  10749      setSelectedLocation(match);
  10750      setUserInput(`${match.localized_name}, ${match.administrative_area.localized_name}`);
  10751    } else if (value.length >= 3 && !match) {
  10752      dispatch(actionCreators.AlsoToMain({
  10753        type: actionTypes.WEATHER_LOCATION_SEARCH_UPDATE,
  10754        data: value
  10755      }));
  10756    }
  10757  }
  10758  function handleCloseSearch() {
  10759    dispatch(actionCreators.BroadcastToContent({
  10760      type: actionTypes.WEATHER_SEARCH_ACTIVE,
  10761      data: false
  10762    }));
  10763    setUserInput("");
  10764  }
  10765  function handleKeyDown(e) {
  10766    if (e.key === "Escape") {
  10767      handleCloseSearch();
  10768    }
  10769  }
  10770  return /*#__PURE__*/external_React_default().createElement("div", {
  10771    className: `${outerClassName} location-search`
  10772  }, /*#__PURE__*/external_React_default().createElement("div", {
  10773    className: "location-input-wrapper"
  10774  }, /*#__PURE__*/external_React_default().createElement("div", {
  10775    className: "search-icon"
  10776  }), /*#__PURE__*/external_React_default().createElement("input", {
  10777    ref: inputRef,
  10778    list: "merino-location-list",
  10779    type: "text",
  10780    "data-l10n-id": "newtab-weather-change-location-search-input-placeholder",
  10781    onChange: handleChange,
  10782    value: userInput,
  10783    onKeyDown: handleKeyDown
  10784  }), /*#__PURE__*/external_React_default().createElement("moz-button", {
  10785    class: "close-icon",
  10786    type: "icon ghost",
  10787    size: "small",
  10788    iconSrc: "chrome://global/skin/icons/close.svg",
  10789    onClick: handleCloseSearch
  10790  }), /*#__PURE__*/external_React_default().createElement("datalist", {
  10791    id: "merino-location-list"
  10792  }, (suggestedLocations || []).map(merinoLocation => /*#__PURE__*/external_React_default().createElement("option", {
  10793    value: merinoLocation.key,
  10794    key: merinoLocation.key
  10795  }, merinoLocation.localized_name, ",", " ", merinoLocation.administrative_area.localized_name)))));
  10796 }
  10797 
  10798 ;// CONCATENATED MODULE: ./content-src/components/Weather/Weather.jsx
  10799 /* This Source Code Form is subject to the terms of the Mozilla Public
  10800 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  10801 * You can obtain one at http://mozilla.org/MPL/2.0/. */
  10802 
  10803 
  10804 
  10805 
  10806 
  10807 
  10808 
  10809 const Weather_VISIBLE = "visible";
  10810 const Weather_VISIBILITY_CHANGE_EVENT = "visibilitychange";
  10811 const PREF_SYSTEM_SHOW_WEATHER = "system.showWeather";
  10812 function WeatherPlaceholder() {
  10813  const [isSeen, setIsSeen] = (0,external_React_namespaceObject.useState)(false);
  10814 
  10815  // We are setting up a visibility and intersection event
  10816  // so animations don't happen with headless automation.
  10817  // The animations causes tests to fail beause they never stop,
  10818  // and many tests wait until everything has stopped before passing.
  10819  const ref = useIntersectionObserver(() => setIsSeen(true), 1);
  10820  const isSeenClassName = isSeen ? `placeholder-seen` : ``;
  10821  return /*#__PURE__*/external_React_default().createElement("div", {
  10822    className: `weather weather-placeholder ${isSeenClassName}`,
  10823    ref: el => {
  10824      ref.current = [el];
  10825    }
  10826  }, /*#__PURE__*/external_React_default().createElement("div", {
  10827    className: "placeholder-image placeholder-fill"
  10828  }), /*#__PURE__*/external_React_default().createElement("div", {
  10829    className: "placeholder-context"
  10830  }, /*#__PURE__*/external_React_default().createElement("div", {
  10831    className: "placeholder-header placeholder-fill"
  10832  }), /*#__PURE__*/external_React_default().createElement("div", {
  10833    className: "placeholder-description placeholder-fill"
  10834  })));
  10835 }
  10836 class _Weather extends (external_React_default()).PureComponent {
  10837  constructor(props) {
  10838    super(props);
  10839    this.state = {
  10840      contextMenuKeyboard: false,
  10841      showContextMenu: false,
  10842      url: "https://example.com",
  10843      impressionSeen: false,
  10844      errorSeen: false
  10845    };
  10846    this.setImpressionRef = element => {
  10847      this.impressionElement = element;
  10848    };
  10849    this.setErrorRef = element => {
  10850      this.errorElement = element;
  10851    };
  10852    this.onClick = this.onClick.bind(this);
  10853    this.onKeyDown = this.onKeyDown.bind(this);
  10854    this.onUpdate = this.onUpdate.bind(this);
  10855    this.onProviderClick = this.onProviderClick.bind(this);
  10856  }
  10857  componentDidMount() {
  10858    const {
  10859      props
  10860    } = this;
  10861    if (!props.dispatch) {
  10862      return;
  10863    }
  10864    if (props.document.visibilityState === Weather_VISIBLE) {
  10865      // Setup the impression observer once the page is visible.
  10866      this.setImpressionObservers();
  10867    } else {
  10868      // We should only ever send the latest impression stats ping, so remove any
  10869      // older listeners.
  10870      if (this._onVisibilityChange) {
  10871        props.document.removeEventListener(Weather_VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
  10872      }
  10873      this._onVisibilityChange = () => {
  10874        if (props.document.visibilityState === Weather_VISIBLE) {
  10875          // Setup the impression observer once the page is visible.
  10876          this.setImpressionObservers();
  10877          props.document.removeEventListener(Weather_VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
  10878        }
  10879      };
  10880      props.document.addEventListener(Weather_VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
  10881    }
  10882  }
  10883  componentWillUnmount() {
  10884    // Remove observers on unmount
  10885    if (this.observer && this.impressionElement) {
  10886      this.observer.unobserve(this.impressionElement);
  10887    }
  10888    if (this.observer && this.errorElement) {
  10889      this.observer.unobserve(this.errorElement);
  10890    }
  10891    if (this._onVisibilityChange) {
  10892      this.props.document.removeEventListener(Weather_VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
  10893    }
  10894  }
  10895  setImpressionObservers() {
  10896    if (this.impressionElement) {
  10897      this.observer = new IntersectionObserver(this.onImpression.bind(this));
  10898      this.observer.observe(this.impressionElement);
  10899    }
  10900    if (this.errorElement) {
  10901      this.observer = new IntersectionObserver(this.onError.bind(this));
  10902      this.observer.observe(this.errorElement);
  10903    }
  10904  }
  10905  onImpression(entries) {
  10906    if (this.state) {
  10907      const entry = entries.find(e => e.isIntersecting);
  10908      if (entry) {
  10909        if (this.impressionElement) {
  10910          this.observer.unobserve(this.impressionElement);
  10911        }
  10912        this.props.dispatch(actionCreators.OnlyToMain({
  10913          type: actionTypes.WEATHER_IMPRESSION
  10914        }));
  10915 
  10916        // Stop observing since element has been seen
  10917        this.setState({
  10918          impressionSeen: true
  10919        });
  10920      }
  10921    }
  10922  }
  10923  onError(entries) {
  10924    if (this.state) {
  10925      const entry = entries.find(e => e.isIntersecting);
  10926      if (entry) {
  10927        if (this.errorElement) {
  10928          this.observer.unobserve(this.errorElement);
  10929        }
  10930        this.props.dispatch(actionCreators.OnlyToMain({
  10931          type: actionTypes.WEATHER_LOAD_ERROR
  10932        }));
  10933 
  10934        // Stop observing since element has been seen
  10935        this.setState({
  10936          errorSeen: true
  10937        });
  10938      }
  10939    }
  10940  }
  10941  openContextMenu(isKeyBoard) {
  10942    if (this.props.onUpdate) {
  10943      this.props.onUpdate(true);
  10944    }
  10945    this.setState({
  10946      showContextMenu: true,
  10947      contextMenuKeyboard: isKeyBoard
  10948    });
  10949  }
  10950  onClick(event) {
  10951    event.preventDefault();
  10952    this.openContextMenu(false, event);
  10953  }
  10954  onKeyDown(event) {
  10955    if (event.key === "Enter" || event.key === " ") {
  10956      event.preventDefault();
  10957      this.openContextMenu(true, event);
  10958    }
  10959  }
  10960  onUpdate(showContextMenu) {
  10961    if (this.props.onUpdate) {
  10962      this.props.onUpdate(showContextMenu);
  10963    }
  10964    this.setState({
  10965      showContextMenu
  10966    });
  10967  }
  10968  onProviderClick() {
  10969    this.props.dispatch(actionCreators.OnlyToMain({
  10970      type: actionTypes.WEATHER_OPEN_PROVIDER_URL,
  10971      data: {
  10972        source: "WEATHER"
  10973      }
  10974    }));
  10975  }
  10976  handleRejectOptIn = () => {
  10977    (0,external_ReactRedux_namespaceObject.batch)(() => {
  10978      this.props.dispatch(actionCreators.SetPref("weather.optInAccepted", false));
  10979      this.props.dispatch(actionCreators.SetPref("weather.optInDisplayed", false));
  10980      this.props.dispatch(actionCreators.AlsoToMain({
  10981        type: actionTypes.WEATHER_OPT_IN_PROMPT_SELECTION,
  10982        data: "rejected opt-in"
  10983      }));
  10984    });
  10985  };
  10986  handleAcceptOptIn = () => {
  10987    (0,external_ReactRedux_namespaceObject.batch)(() => {
  10988      this.props.dispatch(actionCreators.AlsoToMain({
  10989        type: actionTypes.WEATHER_USER_OPT_IN_LOCATION
  10990      }));
  10991      this.props.dispatch(actionCreators.AlsoToMain({
  10992        type: actionTypes.WEATHER_OPT_IN_PROMPT_SELECTION,
  10993        data: "accepted opt-in"
  10994      }));
  10995    });
  10996  };
  10997  isEnabled() {
  10998    const {
  10999      values
  11000    } = this.props.Prefs;
  11001    const systemValue = values[PREF_SYSTEM_SHOW_WEATHER] && values["feeds.weatherfeed"];
  11002    const experimentValue = values.trainhopConfig?.weather?.enabled;
  11003    return systemValue || experimentValue;
  11004  }
  11005  render() {
  11006    // Check if weather should be rendered
  11007    if (!this.isEnabled()) {
  11008      return false;
  11009    }
  11010    if (this.props.App.isForStartupCache.Weather || !this.props.Weather.initialized) {
  11011      return /*#__PURE__*/external_React_default().createElement(WeatherPlaceholder, null);
  11012    }
  11013    const {
  11014      showContextMenu
  11015    } = this.state;
  11016    const {
  11017      props
  11018    } = this;
  11019    const {
  11020      dispatch,
  11021      Prefs,
  11022      Weather
  11023    } = props;
  11024    const WEATHER_SUGGESTION = Weather.suggestions?.[0];
  11025    const outerClassName = ["weather", Weather.searchActive && "search", props.isInSection && "section-weather"].filter(v => v).join(" ");
  11026    const showDetailedView = Prefs.values["weather.display"] === "detailed";
  11027    const weatherOptIn = Prefs.values["system.showWeatherOptIn"];
  11028    const nimbusWeatherOptInEnabled = Prefs.values.trainhopConfig?.weather?.weatherOptInEnabled;
  11029    // Bug 2009484: Controls button order in opt-in dialog for A/B testing.
  11030    // When true, "Not now" gets slot="primary";
  11031    // when false/undefined, "Yes" gets slot="primary".
  11032    // Also note the primary button's position varies by platform:
  11033    // on Windows, it appears on the left,
  11034    // while on Linux and macOS, it appears on the right.
  11035    const reverseOptInButtons = Prefs.values.trainhopConfig?.weather?.reverseOptInButtons;
  11036    const optInDisplayed = Prefs.values["weather.optInDisplayed"];
  11037    const optInUserChoice = Prefs.values["weather.optInAccepted"];
  11038    const staticWeather = Prefs.values["weather.staticData.enabled"];
  11039 
  11040    // Conditionals for rendering feature based on prefs + nimbus experiment variables
  11041    const isOptInEnabled = weatherOptIn || nimbusWeatherOptInEnabled;
  11042 
  11043    // Opt-in dialog should only show if:
  11044    // - weather enabled on customization menu
  11045    // - weather opt-in pref is enabled
  11046    // - opt-in prompt is enabled
  11047    // - user hasn't accepted the opt-in yet
  11048    const shouldShowOptInDialog = isOptInEnabled && optInDisplayed && !optInUserChoice;
  11049 
  11050    // Show static weather data only if:
  11051    // - weather is enabled on customization menu
  11052    // - weather opt-in pref is enabled
  11053    // - static weather data is enabled
  11054    const showStaticData = isOptInEnabled && staticWeather;
  11055 
  11056    // Note: The temperature units/display options will become secondary menu items
  11057    const WEATHER_SOURCE_CONTEXT_MENU_OPTIONS = [...(Prefs.values["weather.locationSearchEnabled"] ? ["ChangeWeatherLocation"] : []), ...(isOptInEnabled ? ["DetectLocation"] : []), ...(Prefs.values["weather.temperatureUnits"] === "f" ? ["ChangeTempUnitCelsius"] : ["ChangeTempUnitFahrenheit"]), ...(Prefs.values["weather.display"] === "simple" ? ["ChangeWeatherDisplayDetailed"] : ["ChangeWeatherDisplaySimple"]), "HideWeather", "OpenLearnMoreURL"];
  11058    const WEATHER_SOURCE_SHORTENED_CONTEXT_MENU_OPTIONS = [...(Prefs.values["weather.locationSearchEnabled"] ? ["ChangeWeatherLocation"] : []), ...(isOptInEnabled ? ["DetectLocation"] : []), "HideWeather", "OpenLearnMoreURL"];
  11059    const contextMenu = contextOpts => /*#__PURE__*/external_React_default().createElement("div", {
  11060      className: "weatherButtonContextMenuWrapper"
  11061    }, /*#__PURE__*/external_React_default().createElement("button", {
  11062      "aria-haspopup": "true",
  11063      onKeyDown: this.onKeyDown,
  11064      onClick: this.onClick,
  11065      "data-l10n-id": "newtab-menu-section-tooltip",
  11066      className: "weatherButtonContextMenu"
  11067    }, showContextMenu ? /*#__PURE__*/external_React_default().createElement(LinkMenu, {
  11068      dispatch: dispatch,
  11069      index: 0,
  11070      source: "WEATHER",
  11071      onUpdate: this.onUpdate,
  11072      options: contextOpts,
  11073      site: {
  11074        url: "https://support.mozilla.org/kb/customize-items-on-firefox-new-tab-page"
  11075      },
  11076      link: "https://support.mozilla.org/kb/customize-items-on-firefox-new-tab-page",
  11077      shouldSendImpressionStats: false
  11078    }) : null));
  11079    if (Weather.searchActive) {
  11080      return /*#__PURE__*/external_React_default().createElement(LocationSearch, {
  11081        outerClassName: outerClassName
  11082      });
  11083    } else if (WEATHER_SUGGESTION) {
  11084      return /*#__PURE__*/external_React_default().createElement("div", {
  11085        ref: this.setImpressionRef,
  11086        className: outerClassName
  11087      }, /*#__PURE__*/external_React_default().createElement("div", {
  11088        className: "weatherCard"
  11089      }, showStaticData ? /*#__PURE__*/external_React_default().createElement("div", {
  11090        className: "weatherInfoLink staticWeatherInfo"
  11091      }, /*#__PURE__*/external_React_default().createElement("div", {
  11092        className: "weatherIconCol"
  11093      }, /*#__PURE__*/external_React_default().createElement("span", {
  11094        className: "weatherIcon iconId3"
  11095      })), /*#__PURE__*/external_React_default().createElement("div", {
  11096        className: "weatherText"
  11097      }, /*#__PURE__*/external_React_default().createElement("div", {
  11098        className: "weatherForecastRow"
  11099      }, /*#__PURE__*/external_React_default().createElement("span", {
  11100        className: "weatherTemperature"
  11101      }, "22\xB0", Prefs.values["weather.temperatureUnits"])), /*#__PURE__*/external_React_default().createElement("div", {
  11102        className: "weatherCityRow"
  11103      }, /*#__PURE__*/external_React_default().createElement("span", {
  11104        className: "weatherCity",
  11105        "data-l10n-id": "newtab-weather-static-city"
  11106      })))) : /*#__PURE__*/external_React_default().createElement("a", {
  11107        "data-l10n-id": "newtab-weather-see-forecast",
  11108        "data-l10n-args": "{\"provider\": \"AccuWeather\xAE\"}",
  11109        href: WEATHER_SUGGESTION.forecast.url,
  11110        className: "weatherInfoLink",
  11111        onClick: this.onProviderClick
  11112      }, /*#__PURE__*/external_React_default().createElement("div", {
  11113        className: "weatherIconCol"
  11114      }, /*#__PURE__*/external_React_default().createElement("span", {
  11115        className: `weatherIcon iconId${WEATHER_SUGGESTION.current_conditions.icon_id}`
  11116      })), /*#__PURE__*/external_React_default().createElement("div", {
  11117        className: "weatherText"
  11118      }, /*#__PURE__*/external_React_default().createElement("div", {
  11119        className: "weatherForecastRow"
  11120      }, /*#__PURE__*/external_React_default().createElement("span", {
  11121        className: "weatherTemperature"
  11122      }, WEATHER_SUGGESTION.current_conditions.temperature[Prefs.values["weather.temperatureUnits"]], "\xB0", Prefs.values["weather.temperatureUnits"])), /*#__PURE__*/external_React_default().createElement("div", {
  11123        className: "weatherCityRow"
  11124      }, /*#__PURE__*/external_React_default().createElement("span", {
  11125        className: "weatherCity"
  11126      }, Weather.locationData.city)), showDetailedView ? /*#__PURE__*/external_React_default().createElement("div", {
  11127        className: "weatherDetailedSummaryRow"
  11128      }, /*#__PURE__*/external_React_default().createElement("div", {
  11129        className: "weatherHighLowTemps"
  11130      }, /*#__PURE__*/external_React_default().createElement("span", null, WEATHER_SUGGESTION.forecast.high[Prefs.values["weather.temperatureUnits"]], "\xB0", Prefs.values["weather.temperatureUnits"]), /*#__PURE__*/external_React_default().createElement("span", null, "\u2022"), /*#__PURE__*/external_React_default().createElement("span", null, WEATHER_SUGGESTION.forecast.low[Prefs.values["weather.temperatureUnits"]], "\xB0", Prefs.values["weather.temperatureUnits"])), /*#__PURE__*/external_React_default().createElement("span", {
  11131        className: "weatherTextSummary"
  11132      }, WEATHER_SUGGESTION.current_conditions.summary)) : null)), contextMenu(showStaticData ? WEATHER_SOURCE_SHORTENED_CONTEXT_MENU_OPTIONS : WEATHER_SOURCE_CONTEXT_MENU_OPTIONS)), /*#__PURE__*/external_React_default().createElement("span", {
  11133        className: "weatherSponsorText"
  11134      }, /*#__PURE__*/external_React_default().createElement("span", {
  11135        "data-l10n-id": "newtab-weather-sponsored",
  11136        "data-l10n-args": "{\"provider\": \"AccuWeather\xAE\"}"
  11137      })), shouldShowOptInDialog && /*#__PURE__*/external_React_default().createElement("div", {
  11138        className: "weatherOptIn"
  11139      }, /*#__PURE__*/external_React_default().createElement("dialog", {
  11140        open: true
  11141      }, /*#__PURE__*/external_React_default().createElement("span", {
  11142        className: "weatherOptInImg"
  11143      }), /*#__PURE__*/external_React_default().createElement("div", {
  11144        className: "weatherOptInContent"
  11145      }, /*#__PURE__*/external_React_default().createElement("h3", {
  11146        "data-l10n-id": "newtab-weather-opt-in-see-weather"
  11147      }), /*#__PURE__*/external_React_default().createElement("moz-button-group", {
  11148        className: "button-group"
  11149      }, /*#__PURE__*/external_React_default().createElement("moz-button", {
  11150        size: "small",
  11151        type: "default",
  11152        "data-l10n-id": "newtab-weather-opt-in-yes",
  11153        onClick: this.handleAcceptOptIn,
  11154        id: "accept-opt-in",
  11155        slot: reverseOptInButtons ? "" : "primary"
  11156      }), /*#__PURE__*/external_React_default().createElement("moz-button", {
  11157        size: "small",
  11158        type: "default",
  11159        "data-l10n-id": "newtab-weather-opt-in-not-now",
  11160        onClick: this.handleRejectOptIn,
  11161        id: "reject-opt-in",
  11162        slot: reverseOptInButtons ? "primary" : ""
  11163      }))))));
  11164    }
  11165    return /*#__PURE__*/external_React_default().createElement("div", {
  11166      ref: this.setErrorRef,
  11167      className: outerClassName
  11168    }, /*#__PURE__*/external_React_default().createElement("div", {
  11169      className: "weatherNotAvailable"
  11170    }, /*#__PURE__*/external_React_default().createElement("span", {
  11171      className: "icon icon-info-warning"
  11172    }), " ", /*#__PURE__*/external_React_default().createElement("p", {
  11173      "data-l10n-id": "newtab-weather-error-not-available"
  11174    }), contextMenu(WEATHER_SOURCE_SHORTENED_CONTEXT_MENU_OPTIONS)));
  11175  }
  11176 }
  11177 const Weather_Weather = (0,external_ReactRedux_namespaceObject.connect)(state => ({
  11178  App: state.App,
  11179  Weather: state.Weather,
  11180  Prefs: state.Prefs,
  11181  IntersectionObserver: globalThis.IntersectionObserver,
  11182  document: globalThis.document
  11183 }))(_Weather);
  11184 ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/CardSections/CardSections.jsx
  11185 /* This Source Code Form is subject to the terms of the Mozilla Public
  11186 * License, v. 2.0. If a copy of the MPL was not distributed with this
  11187 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
  11188 
  11189 
  11190 
  11191 
  11192 
  11193 
  11194 
  11195 
  11196 
  11197 
  11198 
  11199 
  11200 
  11201 
  11202 
  11203 // Prefs
  11204 const CardSections_PREF_SECTIONS_CARDS_ENABLED = "discoverystream.sections.cards.enabled";
  11205 const PREF_SECTIONS_PERSONALIZATION_ENABLED = "discoverystream.sections.personalization.enabled";
  11206 const CardSections_PREF_TOPICS_ENABLED = "discoverystream.topicLabels.enabled";
  11207 const CardSections_PREF_TOPICS_SELECTED = "discoverystream.topicSelection.selectedTopics";
  11208 const CardSections_PREF_TOPICS_AVAILABLE = "discoverystream.topicSelection.topics";
  11209 const PREF_INTEREST_PICKER_ENABLED = "discoverystream.sections.interestPicker.enabled";
  11210 const CardSections_PREF_VISIBLE_SECTIONS = "discoverystream.sections.interestPicker.visibleSections";
  11211 const CardSections_PREF_BILLBOARD_ENABLED = "newtabAdSize.billboard";
  11212 const CardSections_PREF_BILLBOARD_POSITION = "newtabAdSize.billboard.position";
  11213 const CardSections_PREF_LEADERBOARD_ENABLED = "newtabAdSize.leaderboard";
  11214 const CardSections_PREF_LEADERBOARD_POSITION = "newtabAdSize.leaderboard.position";
  11215 const PREF_REFINED_CARDS_ENABLED = "discoverystream.refinedCardsLayout.enabled";
  11216 const PREF_INFERRED_PERSONALIZATION_USER = "discoverystream.sections.personalization.inferred.user.enabled";
  11217 const CardSections_PREF_DAILY_BRIEF_SECTIONID = "discoverystream.dailyBrief.sectionId";
  11218 const CardSections_PREF_SPOCS_STARTUPCACHE_ENABLED = "discoverystream.spocs.startupCache.enabled";
  11219 function getLayoutData(responsiveLayouts, index, refinedCardsLayout) {
  11220  let layoutData = {
  11221    classNames: [],
  11222    imageSizes: {}
  11223  };
  11224  responsiveLayouts.forEach(layout => {
  11225    layout.tiles.forEach((tile, tileIndex) => {
  11226      if (tile.position === index) {
  11227        layoutData.classNames.push(`col-${layout.columnCount}-${tile.size}`);
  11228        layoutData.classNames.push(`col-${layout.columnCount}-position-${tileIndex}`);
  11229        layoutData.imageSizes[layout.columnCount] = tile.size;
  11230 
  11231        // The API tells us whether the tile should show the excerpt or not.
  11232        // Apply extra styles accordingly.
  11233        if (tile.hasExcerpt) {
  11234          if (tile.size === "medium" && refinedCardsLayout) {
  11235            layoutData.classNames.push(`col-${layout.columnCount}-hide-excerpt`);
  11236          } else {
  11237            layoutData.classNames.push(`col-${layout.columnCount}-show-excerpt`);
  11238          }
  11239        } else {
  11240          layoutData.classNames.push(`col-${layout.columnCount}-hide-excerpt`);
  11241        }
  11242      }
  11243    });
  11244  });
  11245  return layoutData;
  11246 }
  11247 
  11248 // function to determine amount of tiles shown per section per viewport
  11249 function getMaxTiles(responsiveLayouts) {
  11250  return responsiveLayouts.flatMap(responsiveLayout => responsiveLayout).reduce((acc, t) => {
  11251    acc[t.columnCount] = t.tiles.length;
  11252 
  11253    // Update maxTile if current tile count is greater
  11254    if (!acc.maxTile || t.tiles.length > acc.maxTile) {
  11255      acc.maxTile = t.tiles.length;
  11256    }
  11257    return acc;
  11258  }, {});
  11259 }
  11260 
  11261 /**
  11262 * Transforms a comma-separated string in user preferences
  11263 * into a cleaned-up array.
  11264 *
  11265 * @param {string} pref - The comma-separated pref to be converted.
  11266 * @returns {string[]} An array of trimmed strings, excluding empty values.
  11267 */
  11268 
  11269 const prefToArray = (pref = "") => {
  11270  return pref.split(",").map(item => item.trim()).filter(item => item);
  11271 };
  11272 function shouldShowOMCHighlight(messageData, componentId) {
  11273  if (!messageData || Object.keys(messageData).length === 0) {
  11274    return false;
  11275  }
  11276  return messageData?.content?.messageType === componentId;
  11277 }
  11278 function CardSection({
  11279  sectionPosition,
  11280  section,
  11281  dispatch,
  11282  type,
  11283  firstVisibleTimestamp,
  11284  ctaButtonVariant,
  11285  ctaButtonSponsors,
  11286  anySectionsFollowed,
  11287  showWeather,
  11288  placeholder
  11289 }) {
  11290  const prefs = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.Prefs.values);
  11291  const {
  11292    messageData
  11293  } = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.Messages);
  11294  const {
  11295    sectionPersonalization
  11296  } = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.DiscoveryStream);
  11297  const {
  11298    isForStartupCache
  11299  } = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.App);
  11300  const [focusedIndex, setFocusedIndex] = (0,external_React_namespaceObject.useState)(0);
  11301  const onCardFocus = index => {
  11302    setFocusedIndex(index);
  11303  };
  11304  const handleCardKeyDown = e => {
  11305    if (e.key === "ArrowLeft" || e.key === "ArrowRight") {
  11306      e.preventDefault();
  11307      const currentCardEl = e.target.closest("article.ds-card");
  11308      if (!currentCardEl) {
  11309        return;
  11310      }
  11311      const activeColumn = getActiveColumnLayout(window.innerWidth);
  11312 
  11313      // Arrow direction should match visual navigation direction in RTL
  11314      const isRTL = document.dir === "rtl";
  11315      const navigateToPrevious = isRTL ? e.key === "ArrowRight" : e.key === "ArrowLeft";
  11316 
  11317      // Extract current position from classList
  11318      let currentPosition = null;
  11319      const positionPrefix = `${activeColumn}-position-`;
  11320      for (let className of currentCardEl.classList) {
  11321        if (className.startsWith(positionPrefix)) {
  11322          currentPosition = parseInt(className.substring(positionPrefix.length), 10);
  11323          break;
  11324        }
  11325      }
  11326      if (currentPosition === null) {
  11327        return;
  11328      }
  11329      const targetPosition = navigateToPrevious ? currentPosition - 1 : currentPosition + 1;
  11330 
  11331      // Find card with target position
  11332      const parentEl = currentCardEl.parentElement;
  11333      if (parentEl) {
  11334        const targetSelector = `article.ds-card.${activeColumn}-position-${targetPosition}`;
  11335        const targetCardEl = parentEl.querySelector(targetSelector);
  11336        if (targetCardEl) {
  11337          const link = targetCardEl.querySelector("a.ds-card-link");
  11338          if (link) {
  11339            link.focus();
  11340          }
  11341        }
  11342      }
  11343    }
  11344  };
  11345  const showTopics = prefs[CardSections_PREF_TOPICS_ENABLED];
  11346  const mayHaveSectionsCards = prefs[CardSections_PREF_SECTIONS_CARDS_ENABLED];
  11347  const selectedTopics = prefs[CardSections_PREF_TOPICS_SELECTED];
  11348  const availableTopics = prefs[CardSections_PREF_TOPICS_AVAILABLE];
  11349  const refinedCardsLayout = prefs[PREF_REFINED_CARDS_ENABLED];
  11350  const spocsStartupCacheEnabled = prefs[CardSections_PREF_SPOCS_STARTUPCACHE_ENABLED];
  11351  const mayHaveSectionsPersonalization = prefs[PREF_SECTIONS_PERSONALIZATION_ENABLED];
  11352  const {
  11353    sectionKey,
  11354    title,
  11355    subtitle
  11356  } = section;
  11357  const {
  11358    responsiveLayouts,
  11359    name: layoutName
  11360  } = section.layout;
  11361  const following = sectionPersonalization[sectionKey]?.isFollowed;
  11362  const handleIntersection = (0,external_React_namespaceObject.useCallback)(() => {
  11363    dispatch(actionCreators.AlsoToMain({
  11364      type: actionTypes.CARD_SECTION_IMPRESSION,
  11365      data: {
  11366        section: sectionKey,
  11367        section_position: sectionPosition,
  11368        is_section_followed: following,
  11369        layout_name: layoutName
  11370      }
  11371    }));
  11372  }, [dispatch, sectionKey, sectionPosition, following, layoutName]);
  11373 
  11374  // Ref to hold the section element
  11375  const sectionRefs = useIntersectionObserver(handleIntersection);
  11376  const onFollowClick = (0,external_React_namespaceObject.useCallback)(() => {
  11377    const updatedSectionData = {
  11378      ...sectionPersonalization,
  11379      [sectionKey]: {
  11380        isFollowed: true,
  11381        isBlocked: false,
  11382        followedAt: new Date().toISOString()
  11383      }
  11384    };
  11385    dispatch(actionCreators.AlsoToMain({
  11386      type: actionTypes.SECTION_PERSONALIZATION_SET,
  11387      data: updatedSectionData
  11388    }));
  11389    // Telemetry Event Dispatch
  11390    dispatch(actionCreators.OnlyToMain({
  11391      type: "FOLLOW_SECTION",
  11392      data: {
  11393        section: sectionKey,
  11394        section_position: sectionPosition,
  11395        event_source: "MOZ_BUTTON"
  11396      }
  11397    }));
  11398  }, [dispatch, sectionPersonalization, sectionKey, sectionPosition]);
  11399  const onUnfollowClick = (0,external_React_namespaceObject.useCallback)(() => {
  11400    const updatedSectionData = {
  11401      ...sectionPersonalization
  11402    };
  11403    delete updatedSectionData[sectionKey];
  11404    dispatch(actionCreators.AlsoToMain({
  11405      type: actionTypes.SECTION_PERSONALIZATION_SET,
  11406      data: updatedSectionData
  11407    }));
  11408 
  11409    // Telemetry Event Dispatch
  11410    dispatch(actionCreators.OnlyToMain({
  11411      type: "UNFOLLOW_SECTION",
  11412      data: {
  11413        section: sectionKey,
  11414        section_position: sectionPosition,
  11415        event_source: "MOZ_BUTTON"
  11416      }
  11417    }));
  11418  }, [dispatch, sectionPersonalization, sectionKey, sectionPosition]);
  11419  let {
  11420    maxTile
  11421  } = getMaxTiles(responsiveLayouts);
  11422  if (placeholder) {
  11423    // We need a number that divides evenly by 2, 3, and 4.
  11424    // So it can be displayed without orphans in grids with 2, 3, and 4 columns.
  11425    maxTile = 12;
  11426  }
  11427  const displaySections = section.data.slice(0, maxTile);
  11428  const isSectionEmpty = !displaySections?.length;
  11429  const shouldShowLabels = sectionKey === "top_stories_section" && showTopics;
  11430  if (isSectionEmpty) {
  11431    return null;
  11432  }
  11433  const sectionContextWrapper = /*#__PURE__*/external_React_default().createElement("div", {
  11434    className: "section-context-wrapper"
  11435  }, /*#__PURE__*/external_React_default().createElement("div", {
  11436    className: following ? "section-follow following" : "section-follow"
  11437  }, !anySectionsFollowed && sectionPosition === 0 && shouldShowOMCHighlight(messageData, "FollowSectionButtonHighlight") && /*#__PURE__*/external_React_default().createElement(MessageWrapper, {
  11438    dispatch: dispatch
  11439  }, /*#__PURE__*/external_React_default().createElement(FollowSectionButtonHighlight, {
  11440    verticalPosition: "inset-block-center",
  11441    position: "arrow-inline-start",
  11442    dispatch: dispatch,
  11443    feature: "FEATURE_FOLLOW_SECTION_BUTTON",
  11444    messageData: messageData
  11445  })), !anySectionsFollowed && sectionPosition === 0 && shouldShowOMCHighlight(messageData, "FollowSectionButtonAltHighlight") && /*#__PURE__*/external_React_default().createElement(MessageWrapper, {
  11446    dispatch: dispatch
  11447  }, /*#__PURE__*/external_React_default().createElement(FollowSectionButtonHighlight, {
  11448    verticalPosition: "inset-block-center",
  11449    position: "arrow-inline-start",
  11450    dispatch: dispatch,
  11451    feature: "FEATURE_ALT_FOLLOW_SECTION_BUTTON"
  11452  })), /*#__PURE__*/external_React_default().createElement("moz-button", {
  11453    onClick: following ? onUnfollowClick : onFollowClick,
  11454    type: "default",
  11455    index: sectionPosition,
  11456    section: sectionKey
  11457  }, /*#__PURE__*/external_React_default().createElement("span", {
  11458    className: "section-button-follow-text",
  11459    "data-l10n-id": "newtab-section-follow-button"
  11460  }), /*#__PURE__*/external_React_default().createElement("span", {
  11461    className: "section-button-following-text",
  11462    "data-l10n-id": "newtab-section-following-button"
  11463  }), /*#__PURE__*/external_React_default().createElement("span", {
  11464    className: "section-button-unfollow-text",
  11465    "data-l10n-id": "newtab-section-unfollow-button"
  11466  }))), /*#__PURE__*/external_React_default().createElement(SectionContextMenu, {
  11467    dispatch: dispatch,
  11468    index: sectionPosition,
  11469    following: following,
  11470    sectionPersonalization: sectionPersonalization,
  11471    sectionKey: sectionKey,
  11472    title: title,
  11473    type: type,
  11474    sectionPosition: sectionPosition
  11475  }));
  11476  return /*#__PURE__*/external_React_default().createElement("section", {
  11477    className: "ds-section",
  11478    ref: el => {
  11479      sectionRefs.current[0] = el;
  11480    }
  11481  }, /*#__PURE__*/external_React_default().createElement("div", {
  11482    className: "section-heading"
  11483  }, /*#__PURE__*/external_React_default().createElement("div", {
  11484    className: "section-heading-inline-start"
  11485  }, /*#__PURE__*/external_React_default().createElement("div", {
  11486    className: "section-title-wrapper"
  11487  }, /*#__PURE__*/external_React_default().createElement("h2", {
  11488    className: "section-title"
  11489  }, title), subtitle && /*#__PURE__*/external_React_default().createElement("p", {
  11490    className: "section-subtitle"
  11491  }, subtitle)), showWeather && /*#__PURE__*/external_React_default().createElement(Weather_Weather, {
  11492    isInSection: true
  11493  })), mayHaveSectionsPersonalization ? sectionContextWrapper : null), /*#__PURE__*/external_React_default().createElement("div", {
  11494    className: `ds-section-grid ds-card-grid`,
  11495    onKeyDown: handleCardKeyDown
  11496  }, section.data.slice(0, maxTile).map((rec, index) => {
  11497    const layoutData = getLayoutData(responsiveLayouts, index, refinedCardsLayout);
  11498    const {
  11499      classNames,
  11500      imageSizes
  11501    } = layoutData;
  11502    // Render a placeholder card when:
  11503    // 1. No recommendation is available.
  11504    // 2. The item is flagged as a placeholder.
  11505    // 3. Spocs are loading for with spocs startup cache disabled.
  11506    if (!rec || rec.placeholder || placeholder || rec.flight_id && !spocsStartupCacheEnabled && isForStartupCache.DiscoveryStream) {
  11507      return /*#__PURE__*/external_React_default().createElement(PlaceholderDSCard, {
  11508        key: `dscard-${index}`
  11509      });
  11510    }
  11511    const card = /*#__PURE__*/external_React_default().createElement(DSCard, {
  11512      key: `dscard-${rec.id}`,
  11513      pos: rec.pos,
  11514      flightId: rec.flight_id,
  11515      image_src: rec.image_src,
  11516      raw_image_src: rec.raw_image_src,
  11517      icon_src: rec.icon_src,
  11518      word_count: rec.word_count,
  11519      time_to_read: rec.time_to_read,
  11520      title: rec.title,
  11521      topic: rec.topic,
  11522      features: rec.features,
  11523      excerpt: rec.excerpt,
  11524      url: rec.url,
  11525      id: rec.id,
  11526      shim: rec.shim,
  11527      fetchTimestamp: rec.fetchTimestamp,
  11528      type: type,
  11529      context: rec.context,
  11530      sponsor: rec.sponsor,
  11531      sponsored_by_override: rec.sponsored_by_override,
  11532      dispatch: dispatch,
  11533      source: rec.domain,
  11534      publisher: rec.publisher,
  11535      pocket_id: rec.pocket_id,
  11536      context_type: rec.context_type,
  11537      bookmarkGuid: rec.bookmarkGuid,
  11538      recommendation_id: rec.recommendation_id,
  11539      firstVisibleTimestamp: firstVisibleTimestamp,
  11540      corpus_item_id: rec.corpus_item_id,
  11541      scheduled_corpus_item_id: rec.scheduled_corpus_item_id,
  11542      recommended_at: rec.recommended_at,
  11543      received_rank: rec.received_rank,
  11544      format: rec.format,
  11545      alt_text: rec.alt_text,
  11546      mayHaveSectionsCards: mayHaveSectionsCards,
  11547      showTopics: shouldShowLabels,
  11548      selectedTopics: selectedTopics,
  11549      availableTopics: availableTopics,
  11550      ctaButtonSponsors: ctaButtonSponsors,
  11551      ctaButtonVariant: ctaButtonVariant,
  11552      sectionsClassNames: classNames.join(" "),
  11553      sectionsCardImageSizes: imageSizes,
  11554      section: sectionKey,
  11555      sectionPosition: sectionPosition,
  11556      sectionFollowed: following,
  11557      sectionLayoutName: layoutName,
  11558      isTimeSensitive: rec.isTimeSensitive,
  11559      tabIndex: index === focusedIndex ? 0 : -1,
  11560      onFocus: () => onCardFocus(index),
  11561      attribution: rec.attribution
  11562    });
  11563    return [card];
  11564  })));
  11565 }
  11566 function CardSections({
  11567  data,
  11568  feed,
  11569  dispatch,
  11570  type,
  11571  firstVisibleTimestamp,
  11572  ctaButtonVariant,
  11573  ctaButtonSponsors,
  11574  placeholder
  11575 }) {
  11576  const prefs = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.Prefs.values);
  11577  const {
  11578    spocs,
  11579    sectionPersonalization
  11580  } = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.DiscoveryStream);
  11581  const {
  11582    messageData
  11583  } = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.Messages);
  11584  const weatherPlacement = (0,external_ReactRedux_namespaceObject.useSelector)(selectWeatherPlacement);
  11585  const dailyBriefSectionId = prefs.trainhopConfig?.dailyBriefing?.sectionId || prefs[CardSections_PREF_DAILY_BRIEF_SECTIONID];
  11586  const weatherEnabled = prefs.showWeather;
  11587  const personalizationEnabled = prefs[PREF_SECTIONS_PERSONALIZATION_ENABLED];
  11588  const interestPickerEnabled = prefs[PREF_INTEREST_PICKER_ENABLED];
  11589 
  11590  // Handle a render before feed has been fetched by displaying nothing
  11591  if (!data) {
  11592    return null;
  11593  }
  11594  const visibleSections = prefToArray(prefs[CardSections_PREF_VISIBLE_SECTIONS]);
  11595  const {
  11596    interestPicker
  11597  } = data;
  11598 
  11599  // Used to determine if we should show FollowSectionButtonHighlight
  11600  const anySectionsFollowed = sectionPersonalization && Object.values(sectionPersonalization).some(section => section?.isFollowed);
  11601  let sectionsData = data.sections;
  11602  if (placeholder) {
  11603    // To clean up the placeholder state for sections if the whole section is loading still.
  11604    sectionsData = [{
  11605      ...sectionsData[0],
  11606      title: "",
  11607      subtitle: ""
  11608    }, {
  11609      ...sectionsData[1],
  11610      title: "",
  11611      subtitle: ""
  11612    }];
  11613  }
  11614  let filteredSections = sectionsData.filter(section => !sectionPersonalization[section.sectionKey]?.isBlocked);
  11615  if (interestPickerEnabled && visibleSections.length) {
  11616    filteredSections = visibleSections.reduce((acc, visibleSection) => {
  11617      const found = filteredSections.find(({
  11618        sectionKey
  11619      }) => sectionKey === visibleSection);
  11620      if (found) {
  11621        acc.push(found);
  11622      }
  11623      return acc;
  11624    }, []);
  11625  }
  11626  let sectionsToRender = filteredSections.map((section, sectionPosition) => /*#__PURE__*/external_React_default().createElement(CardSection, {
  11627    key: `section-${section.sectionKey}`,
  11628    sectionPosition: sectionPosition,
  11629    section: section,
  11630    dispatch: dispatch,
  11631    type: type,
  11632    firstVisibleTimestamp: firstVisibleTimestamp,
  11633    ctaButtonVariant: ctaButtonVariant,
  11634    ctaButtonSponsors: ctaButtonSponsors,
  11635    anySectionsFollowed: anySectionsFollowed,
  11636    placeholder: placeholder,
  11637    showWeather: weatherEnabled && weatherPlacement === "section" && sectionPosition === 0 && section.sectionKey === dailyBriefSectionId
  11638  }));
  11639 
  11640  // Add a billboard/leaderboard IAB ad to the sectionsToRender array (if enabled/possible).
  11641  const billboardEnabled = prefs[CardSections_PREF_BILLBOARD_ENABLED];
  11642  const leaderboardEnabled = prefs[CardSections_PREF_LEADERBOARD_ENABLED];
  11643  if ((billboardEnabled || leaderboardEnabled) && spocs?.data?.newtab_spocs?.items) {
  11644    const spocToRender = spocs.data.newtab_spocs.items.find(({
  11645      format
  11646    }) => format === "leaderboard" && leaderboardEnabled) || spocs.data.newtab_spocs.items.find(({
  11647      format
  11648    }) => format === "billboard" && billboardEnabled);
  11649    if (spocToRender && !spocs.blocked.includes(spocToRender.url)) {
  11650      const row = spocToRender.format === "leaderboard" ? prefs[CardSections_PREF_LEADERBOARD_POSITION] : prefs[CardSections_PREF_BILLBOARD_POSITION];
  11651      sectionsToRender.splice(
  11652      // Math.min is used here to ensure the given row stays within the bounds of the sectionsToRender array.
  11653      Math.min(sectionsToRender.length - 1, row), 0, /*#__PURE__*/external_React_default().createElement(AdBanner, {
  11654        spoc: spocToRender,
  11655        key: `dscard-${spocToRender.id}`,
  11656        dispatch: dispatch,
  11657        type: type,
  11658        firstVisibleTimestamp: firstVisibleTimestamp,
  11659        row: row,
  11660        prefs: prefs
  11661      }));
  11662    }
  11663  }
  11664 
  11665  // Add the interest picker to the sectionsToRender array (if enabled/possible).
  11666  if (interestPickerEnabled && personalizationEnabled && interestPicker?.sections) {
  11667    const index = interestPicker.receivedFeedRank - 1;
  11668    sectionsToRender.splice(
  11669    // Math.min is used here to ensure the given row stays within the bounds of the sectionsToRender array.
  11670    Math.min(sectionsToRender.length - 1, index), 0, /*#__PURE__*/external_React_default().createElement(InterestPicker, {
  11671      title: interestPicker.title,
  11672      subtitle: interestPicker.subtitle,
  11673      interests: interestPicker.sections || [],
  11674      receivedFeedRank: interestPicker.receivedFeedRank
  11675    }));
  11676  }
  11677  function displayP13nCard() {
  11678    if (messageData && Object.keys(messageData).length >= 1) {
  11679      if (shouldShowOMCHighlight(messageData, "PersonalizedCard") && prefs[PREF_INFERRED_PERSONALIZATION_USER]) {
  11680        const row = messageData.content.position;
  11681        sectionsToRender.splice(row, 0, /*#__PURE__*/external_React_default().createElement(MessageWrapper, {
  11682          dispatch: dispatch,
  11683          onDismiss: () => {}
  11684        }, /*#__PURE__*/external_React_default().createElement(PersonalizedCard, {
  11685          position: row,
  11686          dispatch: dispatch,
  11687          messageData: messageData
  11688        })));
  11689      }
  11690    }
  11691  }
  11692  displayP13nCard();
  11693  const isEmpty = sectionsToRender.length === 0;
  11694  return isEmpty ? /*#__PURE__*/external_React_default().createElement("div", {
  11695    className: "ds-card-grid empty"
  11696  }, /*#__PURE__*/external_React_default().createElement(DSEmptyState, {
  11697    status: data.status,
  11698    dispatch: dispatch,
  11699    feed: feed
  11700  })) : /*#__PURE__*/external_React_default().createElement("div", {
  11701    className: "ds-section-wrapper"
  11702  }, sectionsToRender);
  11703 }
  11704 
  11705 ;// CONCATENATED MODULE: ./content-src/components/Widgets/Lists/Lists.jsx
  11706 function Lists_extends() { return Lists_extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, Lists_extends.apply(null, arguments); }
  11707 /* This Source Code Form is subject to the terms of the Mozilla Public
  11708 * License, v. 2.0. If a copy of the MPL was not distributed with this
  11709 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
  11710 
  11711 
  11712 
  11713 
  11714 
  11715 const TASK_TYPE = {
  11716  IN_PROGRESS: "tasks",
  11717  COMPLETED: "completed"
  11718 };
  11719 const USER_ACTION_TYPES = {
  11720  LIST_COPY: "list_copy",
  11721  LIST_CREATE: "list_create",
  11722  LIST_EDIT: "list_edit",
  11723  LIST_DELETE: "list_delete",
  11724  TASK_CREATE: "task_create",
  11725  TASK_EDIT: "task_edit",
  11726  TASK_DELETE: "task_delete",
  11727  TASK_COMPLETE: "task_complete"
  11728 };
  11729 const PREF_WIDGETS_LISTS_MAX_LISTS = "widgets.lists.maxLists";
  11730 const PREF_WIDGETS_LISTS_MAX_LISTITEMS = "widgets.lists.maxListItems";
  11731 const PREF_WIDGETS_LISTS_BADGE_ENABLED = "widgets.lists.badge.enabled";
  11732 const PREF_WIDGETS_LISTS_BADGE_LABEL = "widgets.lists.badge.label";
  11733 function Lists({
  11734  dispatch,
  11735  handleUserInteraction,
  11736  isMaximized
  11737 }) {
  11738  const prefs = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.Prefs.values);
  11739  const {
  11740    selected,
  11741    lists
  11742  } = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.ListsWidget);
  11743  const [newTask, setNewTask] = (0,external_React_namespaceObject.useState)("");
  11744  const [isEditing, setIsEditing] = (0,external_React_namespaceObject.useState)(false);
  11745  const [pendingNewList, setPendingNewList] = (0,external_React_namespaceObject.useState)(null);
  11746  const selectedList = (0,external_React_namespaceObject.useMemo)(() => lists[selected], [lists, selected]);
  11747  const prevCompletedCount = (0,external_React_namespaceObject.useRef)(selectedList?.completed?.length || 0);
  11748  const inputRef = (0,external_React_namespaceObject.useRef)(null);
  11749  const selectRef = (0,external_React_namespaceObject.useRef)(null);
  11750  const reorderListRef = (0,external_React_namespaceObject.useRef)(null);
  11751  const [canvasRef, fireConfetti] = useConfetti();
  11752  const handleListInteraction = (0,external_React_namespaceObject.useCallback)(() => handleUserInteraction("lists"), [handleUserInteraction]);
  11753 
  11754  // store selectedList with useMemo so it isnt re-calculated on every re-render
  11755  const isValidUrl = (0,external_React_namespaceObject.useCallback)(str => URL.canParse(str), []);
  11756  const handleIntersection = (0,external_React_namespaceObject.useCallback)(() => {
  11757    dispatch(actionCreators.AlsoToMain({
  11758      type: actionTypes.WIDGETS_LISTS_USER_IMPRESSION
  11759    }));
  11760  }, [dispatch]);
  11761  const listsRef = useIntersectionObserver(handleIntersection);
  11762  const reorderLists = (0,external_React_namespaceObject.useCallback)((draggedElement, targetElement, before = false) => {
  11763    const draggedIndex = selectedList.tasks.findIndex(({
  11764      id
  11765    }) => id === draggedElement.id);
  11766    const targetIndex = selectedList.tasks.findIndex(({
  11767      id
  11768    }) => id === targetElement.id);
  11769 
  11770    // return early is index is not found
  11771    if (draggedIndex === -1 || targetIndex === -1 || draggedIndex === targetIndex) {
  11772      return;
  11773    }
  11774    const reordered = [...selectedList.tasks];
  11775    const [removed] = reordered.splice(draggedIndex, 1);
  11776    const insertIndex = before ? targetIndex : targetIndex + 1;
  11777    reordered.splice(insertIndex > draggedIndex ? insertIndex - 1 : insertIndex, 0, removed);
  11778    const updatedLists = {
  11779      ...lists,
  11780      [selected]: {
  11781        ...selectedList,
  11782        tasks: reordered
  11783      }
  11784    };
  11785    dispatch(actionCreators.AlsoToMain({
  11786      type: actionTypes.WIDGETS_LISTS_UPDATE,
  11787      data: {
  11788        lists: updatedLists
  11789      }
  11790    }));
  11791    handleListInteraction();
  11792  }, [lists, selected, selectedList, dispatch, handleListInteraction]);
  11793  const moveTask = (0,external_React_namespaceObject.useCallback)((task, direction) => {
  11794    const index = selectedList.tasks.findIndex(({
  11795      id
  11796    }) => id === task.id);
  11797 
  11798    // guardrail a falsey index
  11799    if (index === -1) {
  11800      return;
  11801    }
  11802    const targetIndex = direction === "up" ? index - 1 : index + 1;
  11803    const before = direction === "up";
  11804    const targetTask = selectedList.tasks[targetIndex];
  11805    if (targetTask) {
  11806      reorderLists(task, targetTask, before);
  11807    }
  11808  }, [selectedList, reorderLists]);
  11809  (0,external_React_namespaceObject.useEffect)(() => {
  11810    const selectNode = selectRef.current;
  11811    const reorderNode = reorderListRef.current;
  11812    if (!selectNode || !reorderNode) {
  11813      return undefined;
  11814    }
  11815    function handleSelectChange(e) {
  11816      dispatch(actionCreators.AlsoToMain({
  11817        type: actionTypes.WIDGETS_LISTS_CHANGE_SELECTED,
  11818        data: e.target.value
  11819      }));
  11820      handleListInteraction();
  11821    }
  11822    function handleReorder(e) {
  11823      const {
  11824        draggedElement,
  11825        targetElement,
  11826        position
  11827      } = e.detail;
  11828      reorderLists(draggedElement, targetElement, position === -1);
  11829    }
  11830    reorderNode.addEventListener("reorder", handleReorder);
  11831    selectNode.addEventListener("change", handleSelectChange);
  11832    return () => {
  11833      selectNode.removeEventListener("change", handleSelectChange);
  11834      reorderNode.removeEventListener("reorder", handleReorder);
  11835    };
  11836  }, [dispatch, isEditing, reorderLists, handleListInteraction]);
  11837 
  11838  // effect that enables editing new list name only after store has been hydrated
  11839  (0,external_React_namespaceObject.useEffect)(() => {
  11840    if (selected === pendingNewList) {
  11841      setIsEditing(true);
  11842      setPendingNewList(null);
  11843    }
  11844  }, [selected, pendingNewList]);
  11845  function saveTask() {
  11846    const trimmedTask = newTask.trimEnd();
  11847    // only add new task if it has a length, to avoid creating empty tasks
  11848    if (trimmedTask) {
  11849      const formattedTask = {
  11850        value: trimmedTask,
  11851        completed: false,
  11852        created: Date.now(),
  11853        id: crypto.randomUUID(),
  11854        isUrl: isValidUrl(trimmedTask)
  11855      };
  11856      const updatedLists = {
  11857        ...lists,
  11858        [selected]: {
  11859          ...selectedList,
  11860          tasks: [formattedTask, ...lists[selected].tasks]
  11861        }
  11862      };
  11863      (0,external_ReactRedux_namespaceObject.batch)(() => {
  11864        dispatch(actionCreators.AlsoToMain({
  11865          type: actionTypes.WIDGETS_LISTS_UPDATE,
  11866          data: {
  11867            lists: updatedLists
  11868          }
  11869        }));
  11870        dispatch(actionCreators.OnlyToMain({
  11871          type: actionTypes.WIDGETS_LISTS_USER_EVENT,
  11872          data: {
  11873            userAction: USER_ACTION_TYPES.TASK_CREATE
  11874          }
  11875        }));
  11876      });
  11877      setNewTask("");
  11878      handleListInteraction();
  11879    }
  11880  }
  11881  function updateTask(updatedTask, type) {
  11882    const isCompletedType = type === TASK_TYPE.COMPLETED;
  11883    const isNowCompleted = updatedTask.completed;
  11884    let newTasks = selectedList.tasks;
  11885    let newCompleted = selectedList.completed;
  11886    let userAction;
  11887 
  11888    // If the task is in the completed array and is now unchecked
  11889    const shouldMoveToTasks = isCompletedType && !isNowCompleted;
  11890 
  11891    // If we're moving the task from tasks → completed (user checked it)
  11892    const shouldMoveToCompleted = !isCompletedType && isNowCompleted;
  11893 
  11894    //  Move task from completed -> task
  11895    if (shouldMoveToTasks) {
  11896      newCompleted = selectedList.completed.filter(task => task.id !== updatedTask.id);
  11897      newTasks = [...selectedList.tasks, updatedTask];
  11898      // Move task to completed, but also create local version
  11899    } else if (shouldMoveToCompleted) {
  11900      newTasks = selectedList.tasks.filter(task => task.id !== updatedTask.id);
  11901      newCompleted = [...selectedList.completed, updatedTask];
  11902      userAction = USER_ACTION_TYPES.TASK_COMPLETE;
  11903    } else {
  11904      const targetKey = isCompletedType ? "completed" : "tasks";
  11905      const updatedArray = selectedList[targetKey].map(task => task.id === updatedTask.id ? updatedTask : task);
  11906      // In-place update: toggle checkbox (but stay in same array or edit name)
  11907      if (targetKey === "tasks") {
  11908        newTasks = updatedArray;
  11909      } else {
  11910        newCompleted = updatedArray;
  11911      }
  11912      userAction = USER_ACTION_TYPES.TASK_EDIT;
  11913    }
  11914    const updatedLists = {
  11915      ...lists,
  11916      [selected]: {
  11917        ...selectedList,
  11918        tasks: newTasks,
  11919        completed: newCompleted
  11920      }
  11921    };
  11922    (0,external_ReactRedux_namespaceObject.batch)(() => {
  11923      dispatch(actionCreators.AlsoToMain({
  11924        type: actionTypes.WIDGETS_LISTS_UPDATE,
  11925        data: {
  11926          lists: updatedLists
  11927        }
  11928      }));
  11929      if (userAction) {
  11930        dispatch(actionCreators.AlsoToMain({
  11931          type: actionTypes.WIDGETS_LISTS_USER_EVENT,
  11932          data: {
  11933            userAction
  11934          }
  11935        }));
  11936      }
  11937    });
  11938    handleListInteraction();
  11939  }
  11940  function deleteTask(task, type) {
  11941    const selectedTasks = lists[selected][type];
  11942    const updatedTasks = selectedTasks.filter(({
  11943      id
  11944    }) => id !== task.id);
  11945    const updatedLists = {
  11946      ...lists,
  11947      [selected]: {
  11948        ...selectedList,
  11949        [type]: updatedTasks
  11950      }
  11951    };
  11952    (0,external_ReactRedux_namespaceObject.batch)(() => {
  11953      dispatch(actionCreators.AlsoToMain({
  11954        type: actionTypes.WIDGETS_LISTS_UPDATE,
  11955        data: {
  11956          lists: updatedLists
  11957        }
  11958      }));
  11959      dispatch(actionCreators.OnlyToMain({
  11960        type: actionTypes.WIDGETS_LISTS_USER_EVENT,
  11961        data: {
  11962          userAction: USER_ACTION_TYPES.TASK_DELETE
  11963        }
  11964      }));
  11965    });
  11966    handleListInteraction();
  11967  }
  11968  function handleKeyDown(e) {
  11969    if (e.key === "Enter" && document.activeElement === inputRef.current) {
  11970      saveTask();
  11971    } else if (e.key === "Escape" && document.activeElement === inputRef.current) {
  11972      // Clear out the input when esc is pressed
  11973      setNewTask("");
  11974    }
  11975  }
  11976  function handleListNameSave(newLabel) {
  11977    const trimmedLabel = newLabel.trimEnd();
  11978    if (trimmedLabel && trimmedLabel !== selectedList?.label) {
  11979      const updatedLists = {
  11980        ...lists,
  11981        [selected]: {
  11982          ...selectedList,
  11983          label: trimmedLabel
  11984        }
  11985      };
  11986      (0,external_ReactRedux_namespaceObject.batch)(() => {
  11987        dispatch(actionCreators.AlsoToMain({
  11988          type: actionTypes.WIDGETS_LISTS_UPDATE,
  11989          data: {
  11990            lists: updatedLists
  11991          }
  11992        }));
  11993        dispatch(actionCreators.OnlyToMain({
  11994          type: actionTypes.WIDGETS_LISTS_USER_EVENT,
  11995          data: {
  11996            userAction: USER_ACTION_TYPES.LIST_EDIT
  11997          }
  11998        }));
  11999      });
  12000      setIsEditing(false);
  12001      handleListInteraction();
  12002    }
  12003  }
  12004  function handleCreateNewList() {
  12005    const id = crypto.randomUUID();
  12006    const newLists = {
  12007      ...lists,
  12008      [id]: {
  12009        label: "",
  12010        tasks: [],
  12011        completed: []
  12012      }
  12013    };
  12014    (0,external_ReactRedux_namespaceObject.batch)(() => {
  12015      dispatch(actionCreators.AlsoToMain({
  12016        type: actionTypes.WIDGETS_LISTS_UPDATE,
  12017        data: {
  12018          lists: newLists
  12019        }
  12020      }));
  12021      dispatch(actionCreators.AlsoToMain({
  12022        type: actionTypes.WIDGETS_LISTS_CHANGE_SELECTED,
  12023        data: id
  12024      }));
  12025      dispatch(actionCreators.OnlyToMain({
  12026        type: actionTypes.WIDGETS_LISTS_USER_EVENT,
  12027        data: {
  12028          userAction: USER_ACTION_TYPES.LIST_CREATE
  12029        }
  12030      }));
  12031    });
  12032    setPendingNewList(id);
  12033    handleListInteraction();
  12034  }
  12035  function handleCancelNewList() {
  12036    // If current list is new and has no label/tasks, remove it
  12037    if (!selectedList?.label && selectedList?.tasks?.length === 0) {
  12038      const updatedLists = {
  12039        ...lists
  12040      };
  12041      delete updatedLists[selected];
  12042      const listKeys = Object.keys(updatedLists);
  12043      const key = listKeys[listKeys.length - 1];
  12044      (0,external_ReactRedux_namespaceObject.batch)(() => {
  12045        dispatch(actionCreators.AlsoToMain({
  12046          type: actionTypes.WIDGETS_LISTS_UPDATE,
  12047          data: {
  12048            lists: updatedLists
  12049          }
  12050        }));
  12051        dispatch(actionCreators.AlsoToMain({
  12052          type: actionTypes.WIDGETS_LISTS_CHANGE_SELECTED,
  12053          data: key
  12054        }));
  12055        dispatch(actionCreators.OnlyToMain({
  12056          type: actionTypes.WIDGETS_LISTS_USER_EVENT,
  12057          data: {
  12058            userAction: USER_ACTION_TYPES.LIST_DELETE
  12059          }
  12060        }));
  12061      });
  12062    }
  12063    handleListInteraction();
  12064  }
  12065  function handleDeleteList() {
  12066    let updatedLists = {
  12067      ...lists
  12068    };
  12069    if (updatedLists[selected]) {
  12070      delete updatedLists[selected];
  12071 
  12072      // if this list was the last one created, add a new list as default
  12073      if (Object.keys(updatedLists)?.length === 0) {
  12074        updatedLists = {
  12075          [crypto.randomUUID()]: {
  12076            label: "",
  12077            tasks: [],
  12078            completed: []
  12079          }
  12080        };
  12081      }
  12082      const listKeys = Object.keys(updatedLists);
  12083      const key = listKeys[listKeys.length - 1];
  12084      (0,external_ReactRedux_namespaceObject.batch)(() => {
  12085        dispatch(actionCreators.AlsoToMain({
  12086          type: actionTypes.WIDGETS_LISTS_UPDATE,
  12087          data: {
  12088            lists: updatedLists
  12089          }
  12090        }));
  12091        dispatch(actionCreators.AlsoToMain({
  12092          type: actionTypes.WIDGETS_LISTS_CHANGE_SELECTED,
  12093          data: key
  12094        }));
  12095        dispatch(actionCreators.OnlyToMain({
  12096          type: actionTypes.WIDGETS_LISTS_USER_EVENT,
  12097          data: {
  12098            userAction: USER_ACTION_TYPES.LIST_DELETE
  12099          }
  12100        }));
  12101      });
  12102    }
  12103    handleListInteraction();
  12104  }
  12105  function handleHideLists() {
  12106    dispatch(actionCreators.OnlyToMain({
  12107      type: actionTypes.SET_PREF,
  12108      data: {
  12109        name: "widgets.lists.enabled",
  12110        value: false
  12111      }
  12112    }));
  12113    handleListInteraction();
  12114  }
  12115  function handleCopyListToClipboard() {
  12116    const currentList = lists[selected];
  12117    if (!currentList) {
  12118      return;
  12119    }
  12120    const {
  12121      label,
  12122      tasks = [],
  12123      completed = []
  12124    } = currentList;
  12125    const uncompleted = tasks.filter(task => !task.completed);
  12126    const currentCompleted = tasks.filter(task => task.completed);
  12127 
  12128    // In order in include all items, we need to iterate through both current and completed tasks list and mark format all completed tasks accordingly.
  12129    const formatted = [`List: ${label}`, `---`, ...uncompleted.map(task => `- [ ] ${task.value}`), ...currentCompleted.map(task => `- [x] ${task.value}`), ...completed.map(task => `- [x] ${task.value}`)].join("\n");
  12130    try {
  12131      navigator.clipboard.writeText(formatted);
  12132    } catch (err) {
  12133      console.error("Copy failed", err);
  12134    }
  12135    dispatch(actionCreators.OnlyToMain({
  12136      type: actionTypes.WIDGETS_LISTS_USER_EVENT,
  12137      data: {
  12138        userAction: USER_ACTION_TYPES.LIST_COPY
  12139      }
  12140    }));
  12141    handleListInteraction();
  12142  }
  12143  function handleLearnMore() {
  12144    dispatch(actionCreators.OnlyToMain({
  12145      type: actionTypes.OPEN_LINK,
  12146      data: {
  12147        url: "https://support.mozilla.org/kb/firefox-new-tab-widgets"
  12148      }
  12149    }));
  12150    handleListInteraction();
  12151  }
  12152 
  12153  // Reset baseline only when switching lists
  12154  (0,external_React_namespaceObject.useEffect)(() => {
  12155    prevCompletedCount.current = selectedList?.completed?.length || 0;
  12156    // intentionally leaving out selectedList from dependency array
  12157    // eslint-disable-next-line react-hooks/exhaustive-deps
  12158  }, [selected]);
  12159  (0,external_React_namespaceObject.useEffect)(() => {
  12160    if (selectedList) {
  12161      const doneCount = selectedList.completed?.length || 0;
  12162      const previous = Math.floor(prevCompletedCount.current / 5);
  12163      const current = Math.floor(doneCount / 5);
  12164      if (current > previous) {
  12165        fireConfetti();
  12166      }
  12167      prevCompletedCount.current = doneCount;
  12168    }
  12169  }, [selectedList, fireConfetti, selected]);
  12170  if (!lists) {
  12171    return null;
  12172  }
  12173 
  12174  // Enforce maximum count limits to lists
  12175  const currentListsCount = Object.keys(lists).length;
  12176  // Ensure a minimum of 1, but allow higher values from prefs
  12177  const maxListsCount = Math.max(1, prefs[PREF_WIDGETS_LISTS_MAX_LISTS]);
  12178  const isAtMaxListsLimit = currentListsCount >= maxListsCount;
  12179 
  12180  // Enforce maximum count limits to list items
  12181  // The maximum applies to the total number of items (both incomplete and completed items)
  12182  const currentSelectedListItemsCount = selectedList?.tasks.length + selectedList?.completed.length;
  12183 
  12184  // Ensure a minimum of 1, but allow higher values from prefs
  12185  const maxListItemsCount = Math.max(1, prefs[PREF_WIDGETS_LISTS_MAX_LISTITEMS]);
  12186  const isAtMaxListItemsLimit = currentSelectedListItemsCount >= maxListItemsCount;
  12187 
  12188  // Figure out if the selected list is the first (default) or a new one.
  12189  // Index 0 → use "Task list"; any later index → use "New list".
  12190  // Fallback to 0 if the selected id isn’t found.
  12191  const listKeys = Object.keys(lists);
  12192  const selectedIndex = Math.max(0, listKeys.indexOf(selected));
  12193  const listNamePlaceholder = currentListsCount > 1 && selectedIndex !== 0 ? "newtab-widget-lists-name-placeholder-new" : "newtab-widget-lists-name-placeholder-default";
  12194  const nimbusBadgeEnabled = prefs.widgetsConfig?.listsBadgeEnabled;
  12195  const nimbusBadgeLabel = prefs.widgetsConfig?.listsBadgeLabel;
  12196  const nimbusBadgeTrainhopEnabled = prefs.trainhopConfig?.widgets?.listsBadgeEnabled;
  12197  const nimbusBadgeTrainhopLabel = prefs.trainhopConfig?.widgets?.listsBadgeLabel;
  12198  const badgeEnabled = (nimbusBadgeEnabled || nimbusBadgeTrainhopEnabled) ?? prefs[PREF_WIDGETS_LISTS_BADGE_ENABLED] ?? false;
  12199  const badgeLabel = (nimbusBadgeLabel || nimbusBadgeTrainhopLabel) ?? prefs[PREF_WIDGETS_LISTS_BADGE_LABEL] ?? "";
  12200  return /*#__PURE__*/external_React_default().createElement("article", {
  12201    className: `lists ${isMaximized ? "is-maximized" : ""}`,
  12202    ref: el => {
  12203      listsRef.current = [el];
  12204    }
  12205  }, /*#__PURE__*/external_React_default().createElement("div", {
  12206    className: "select-wrapper"
  12207  }, /*#__PURE__*/external_React_default().createElement(EditableText, {
  12208    value: lists[selected]?.label || "",
  12209    onSave: handleListNameSave,
  12210    isEditing: isEditing,
  12211    setIsEditing: setIsEditing,
  12212    onCancel: handleCancelNewList,
  12213    type: "list",
  12214    maxLength: 30,
  12215    dataL10nId: listNamePlaceholder
  12216  }, /*#__PURE__*/external_React_default().createElement("moz-select", {
  12217    ref: selectRef,
  12218    value: selected
  12219  }, Object.entries(lists).map(([key, list]) => /*#__PURE__*/external_React_default().createElement("moz-option", Lists_extends({
  12220    key: key,
  12221    value: key
  12222    // On the first/initial list, use default name
  12223  }, list.label ? {
  12224    label: list.label
  12225  } : {
  12226    "data-l10n-id": "newtab-widget-lists-name-label-default"
  12227  }))))), !isEditing && badgeEnabled && badgeLabel && /*#__PURE__*/external_React_default().createElement("moz-badge", {
  12228    "data-l10n-id": (() => {
  12229      if (badgeLabel === "New") {
  12230        return "newtab-widget-lists-label-new";
  12231      }
  12232      if (badgeLabel === "Beta") {
  12233        return "newtab-widget-lists-label-beta";
  12234      }
  12235      return "";
  12236    })()
  12237  }), /*#__PURE__*/external_React_default().createElement("moz-button", {
  12238    className: "lists-panel-button",
  12239    iconSrc: "chrome://global/skin/icons/more.svg",
  12240    menuId: "lists-panel",
  12241    type: "ghost"
  12242  }), /*#__PURE__*/external_React_default().createElement("panel-list", {
  12243    id: "lists-panel"
  12244  }, /*#__PURE__*/external_React_default().createElement("panel-item", {
  12245    "data-l10n-id": "newtab-widget-lists-menu-edit",
  12246    onClick: () => setIsEditing(true)
  12247  }), /*#__PURE__*/external_React_default().createElement("panel-item", Lists_extends({}, isAtMaxListsLimit ? {
  12248    disabled: true
  12249  } : {}, {
  12250    "data-l10n-id": "newtab-widget-lists-menu-create",
  12251    onClick: () => handleCreateNewList(),
  12252    className: "create-list"
  12253  })), /*#__PURE__*/external_React_default().createElement("panel-item", {
  12254    "data-l10n-id": "newtab-widget-lists-menu-delete",
  12255    onClick: () => handleDeleteList()
  12256  }), /*#__PURE__*/external_React_default().createElement("hr", null), /*#__PURE__*/external_React_default().createElement("panel-item", {
  12257    "data-l10n-id": "newtab-widget-lists-menu-copy",
  12258    onClick: () => handleCopyListToClipboard()
  12259  }), /*#__PURE__*/external_React_default().createElement("panel-item", {
  12260    "data-l10n-id": "newtab-widget-lists-menu-hide",
  12261    onClick: () => handleHideLists()
  12262  }), /*#__PURE__*/external_React_default().createElement("panel-item", {
  12263    className: "learn-more",
  12264    "data-l10n-id": "newtab-widget-lists-menu-learn-more",
  12265    onClick: handleLearnMore
  12266  }))), /*#__PURE__*/external_React_default().createElement("div", {
  12267    className: "add-task-container"
  12268  }, /*#__PURE__*/external_React_default().createElement("span", {
  12269    className: `icon icon-add ${isAtMaxListItemsLimit ? "icon-disabled" : ""}`
  12270  }), /*#__PURE__*/external_React_default().createElement("input", {
  12271    ref: inputRef,
  12272    onBlur: () => saveTask(),
  12273    onChange: e => setNewTask(e.target.value),
  12274    value: newTask,
  12275    "data-l10n-id": "newtab-widget-lists-input-add-an-item",
  12276    className: "add-task-input",
  12277    onKeyDown: handleKeyDown,
  12278    type: "text",
  12279    maxLength: 100,
  12280    disabled: isAtMaxListItemsLimit
  12281  })), /*#__PURE__*/external_React_default().createElement("div", {
  12282    className: "task-list-wrapper"
  12283  }, /*#__PURE__*/external_React_default().createElement("moz-reorderable-list", {
  12284    ref: reorderListRef,
  12285    itemSelector: "fieldset .task-type-tasks",
  12286    dragSelector: ".checkbox-wrapper:has(.task-label)"
  12287  }, /*#__PURE__*/external_React_default().createElement("fieldset", null, selectedList?.tasks.length >= 1 && selectedList.tasks.map((task, index) => /*#__PURE__*/external_React_default().createElement(ListItem, {
  12288    type: TASK_TYPE.IN_PROGRESS,
  12289    task: task,
  12290    key: task.id,
  12291    updateTask: updateTask,
  12292    deleteTask: deleteTask,
  12293    moveTask: moveTask,
  12294    isValidUrl: isValidUrl,
  12295    isFirst: index === 0,
  12296    isLast: index === selectedList.tasks.length - 1
  12297  })), selectedList?.completed.length >= 1 && /*#__PURE__*/external_React_default().createElement("details", {
  12298    className: "completed-task-wrapper",
  12299    open: selectedList?.tasks.length < 1
  12300  }, /*#__PURE__*/external_React_default().createElement("summary", null, /*#__PURE__*/external_React_default().createElement("span", {
  12301    "data-l10n-id": "newtab-widget-lists-completed-list",
  12302    "data-l10n-args": JSON.stringify({
  12303      number: lists[selected]?.completed.length
  12304    }),
  12305    className: "completed-title"
  12306  })), selectedList?.completed.map(completedTask => /*#__PURE__*/external_React_default().createElement(ListItem, {
  12307    key: completedTask.id,
  12308    type: TASK_TYPE.COMPLETED,
  12309    task: completedTask,
  12310    deleteTask: deleteTask,
  12311    updateTask: updateTask
  12312  }))))), selectedList?.tasks.length < 1 && selectedList?.completed.length < 1 && /*#__PURE__*/external_React_default().createElement("div", {
  12313    className: "empty-list"
  12314  }, /*#__PURE__*/external_React_default().createElement("picture", null, /*#__PURE__*/external_React_default().createElement("source", {
  12315    srcSet: "chrome://newtab/content/data/content/assets/lists-empty-state-dark.svg",
  12316    media: "(prefers-color-scheme: dark)"
  12317  }), /*#__PURE__*/external_React_default().createElement("source", {
  12318    srcSet: "chrome://newtab/content/data/content/assets/lists-empty-state-light.svg",
  12319    media: "(prefers-color-scheme: light)"
  12320  }), /*#__PURE__*/external_React_default().createElement("img", {
  12321    width: "100",
  12322    height: "100",
  12323    alt: ""
  12324  })), /*#__PURE__*/external_React_default().createElement("p", {
  12325    className: "empty-list-text",
  12326    "data-l10n-id": "newtab-widget-lists-empty-cta"
  12327  }))), /*#__PURE__*/external_React_default().createElement("canvas", {
  12328    className: "confetti-canvas",
  12329    ref: canvasRef
  12330  }));
  12331 }
  12332 function ListItem({
  12333  task,
  12334  updateTask,
  12335  deleteTask,
  12336  moveTask,
  12337  isValidUrl,
  12338  type,
  12339  isFirst = false,
  12340  isLast = false
  12341 }) {
  12342  const [isEditing, setIsEditing] = (0,external_React_namespaceObject.useState)(false);
  12343  const [exiting, setExiting] = (0,external_React_namespaceObject.useState)(false);
  12344  const isCompleted = type === TASK_TYPE.COMPLETED;
  12345  const prefersReducedMotion = typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
  12346  function handleCheckboxChange(e) {
  12347    const {
  12348      checked
  12349    } = e.target;
  12350    const updatedTask = {
  12351      ...task,
  12352      completed: checked
  12353    };
  12354    if (checked && !prefersReducedMotion) {
  12355      setExiting(true);
  12356    } else {
  12357      updateTask(updatedTask, type);
  12358    }
  12359  }
  12360 
  12361  // When the CSS transition finishes, dispatch the real “completed = true”
  12362  function handleTransitionEnd(e) {
  12363    // only fire once for the exit:
  12364    if (e.propertyName === "opacity" && exiting) {
  12365      updateTask({
  12366        ...task,
  12367        completed: true
  12368      }, type);
  12369      setExiting(false);
  12370    }
  12371  }
  12372  function handleSave(newValue) {
  12373    const trimmedTask = newValue.trimEnd();
  12374    if (trimmedTask && trimmedTask !== task.value) {
  12375      updateTask({
  12376        ...task,
  12377        value: newValue,
  12378        isUrl: isValidUrl(trimmedTask)
  12379      }, type);
  12380      setIsEditing(false);
  12381    }
  12382  }
  12383  function handleDelete() {
  12384    deleteTask(task, type);
  12385  }
  12386  const taskLabel = task.isUrl ? /*#__PURE__*/external_React_default().createElement("a", {
  12387    href: task.value,
  12388    rel: "noopener noreferrer",
  12389    target: "_blank",
  12390    className: "task-label",
  12391    title: task.value
  12392  }, task.value) : /*#__PURE__*/external_React_default().createElement("label", {
  12393    className: "task-label",
  12394    title: task.value,
  12395    htmlFor: `task-${task.id}`,
  12396    onClick: () => setIsEditing(true)
  12397  }, task.value);
  12398  return /*#__PURE__*/external_React_default().createElement("div", {
  12399    className: `task-item task-type-${type} ${exiting ? " exiting" : ""}`,
  12400    id: task.id,
  12401    key: task.id,
  12402    onTransitionEnd: handleTransitionEnd
  12403  }, /*#__PURE__*/external_React_default().createElement("div", {
  12404    className: "checkbox-wrapper",
  12405    key: isEditing
  12406  }, /*#__PURE__*/external_React_default().createElement("input", {
  12407    type: "checkbox",
  12408    onChange: handleCheckboxChange,
  12409    checked: task.completed || exiting,
  12410    id: `task-${task.id}`
  12411  }), isCompleted ? taskLabel : /*#__PURE__*/external_React_default().createElement(EditableText, {
  12412    isEditing: isEditing,
  12413    setIsEditing: setIsEditing,
  12414    value: task.value,
  12415    onSave: handleSave,
  12416    type: "task"
  12417  }, taskLabel)), /*#__PURE__*/external_React_default().createElement("moz-button", {
  12418    iconSrc: "chrome://global/skin/icons/more.svg",
  12419    menuId: `panel-task-${task.id}`,
  12420    type: "ghost"
  12421  }), /*#__PURE__*/external_React_default().createElement("panel-list", {
  12422    id: `panel-task-${task.id}`
  12423  }, !isCompleted && /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, task.isUrl && /*#__PURE__*/external_React_default().createElement("panel-item", {
  12424    "data-l10n-id": "newtab-widget-lists-input-menu-open-link",
  12425    onClick: () => window.open(task.value, "_blank", "noopener")
  12426  }), /*#__PURE__*/external_React_default().createElement("panel-item", Lists_extends({}, isFirst ? {
  12427    disabled: true
  12428  } : {}, {
  12429    onClick: () => moveTask(task, "up"),
  12430    "data-l10n-id": "newtab-widget-lists-input-menu-move-up"
  12431  })), /*#__PURE__*/external_React_default().createElement("panel-item", Lists_extends({}, isLast ? {
  12432    disabled: true
  12433  } : {}, {
  12434    onClick: () => moveTask(task, "down"),
  12435    "data-l10n-id": "newtab-widget-lists-input-menu-move-down"
  12436  })), /*#__PURE__*/external_React_default().createElement("panel-item", {
  12437    "data-l10n-id": "newtab-widget-lists-input-menu-edit",
  12438    className: "edit-item",
  12439    onClick: () => setIsEditing(true)
  12440  })), /*#__PURE__*/external_React_default().createElement("panel-item", {
  12441    "data-l10n-id": "newtab-widget-lists-input-menu-delete",
  12442    className: "delete-item",
  12443    onClick: handleDelete
  12444  })));
  12445 }
  12446 function EditableText({
  12447  value,
  12448  isEditing,
  12449  setIsEditing,
  12450  onSave,
  12451  onCancel,
  12452  children,
  12453  type,
  12454  dataL10nId = null,
  12455  maxLength = 100
  12456 }) {
  12457  const [tempValue, setTempValue] = (0,external_React_namespaceObject.useState)(value);
  12458  const inputRef = (0,external_React_namespaceObject.useRef)(null);
  12459 
  12460  // True if tempValue is empty, null/undefined, or only whitespace
  12461  const showPlaceholder = (tempValue ?? "").trim() === "";
  12462  (0,external_React_namespaceObject.useEffect)(() => {
  12463    if (isEditing) {
  12464      inputRef.current?.focus();
  12465    } else {
  12466      setTempValue(value);
  12467    }
  12468  }, [isEditing, value]);
  12469  function handleKeyDown(e) {
  12470    if (e.key === "Enter") {
  12471      onSave(tempValue.trim());
  12472      setIsEditing(false);
  12473    } else if (e.key === "Escape") {
  12474      setIsEditing(false);
  12475      setTempValue(value);
  12476      onCancel?.();
  12477    }
  12478  }
  12479  function handleOnBlur() {
  12480    onSave(tempValue.trim());
  12481    setIsEditing(false);
  12482  }
  12483  return isEditing ? /*#__PURE__*/external_React_default().createElement("input", Lists_extends({
  12484    className: `edit-${type}`,
  12485    ref: inputRef,
  12486    type: "text",
  12487    value: tempValue,
  12488    maxLength: maxLength,
  12489    onChange: event => setTempValue(event.target.value),
  12490    onBlur: handleOnBlur,
  12491    onKeyDown: handleKeyDown
  12492    // Note that if a user has a custom name set, it will override the placeholder
  12493  }, showPlaceholder && dataL10nId ? {
  12494    "data-l10n-id": dataL10nId
  12495  } : {})) : [children];
  12496 }
  12497 
  12498 ;// CONCATENATED MODULE: ./content-src/components/Widgets/FocusTimer/FocusTimer.jsx
  12499 function FocusTimer_extends() { return FocusTimer_extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, FocusTimer_extends.apply(null, arguments); }
  12500 /* This Source Code Form is subject to the terms of the Mozilla Public
  12501 * License, v. 2.0. If a copy of the MPL was not distributed with this
  12502 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
  12503 
  12504 
  12505 
  12506 
  12507 
  12508 const FocusTimer_USER_ACTION_TYPES = {
  12509  TIMER_SET: "timer_set",
  12510  TIMER_PLAY: "timer_play",
  12511  TIMER_PAUSE: "timer_pause",
  12512  TIMER_RESET: "timer_reset",
  12513  TIMER_END: "timer_end",
  12514  TIMER_TOGGLE_FOCUS: "timer_toggle_focus",
  12515  TIMER_TOGGLE_BREAK: "timer_toggle_break"
  12516 };
  12517 
  12518 /**
  12519 * Calculates the remaining time (in seconds) by subtracting elapsed time from the original duration
  12520 *
  12521 * @param duration
  12522 * @param start
  12523 * @returns int
  12524 */
  12525 const calculateTimeRemaining = (duration, start) => {
  12526  const currentTime = Math.floor(Date.now() / 1000);
  12527 
  12528  // Subtract the elapsed time from initial duration to get time remaining in the timer
  12529  return Math.max(duration - (currentTime - start), 0);
  12530 };
  12531 
  12532 /**
  12533 * Converts a number of seconds into a zero-padded MM:SS time string
  12534 *
  12535 * @param seconds
  12536 * @returns string
  12537 */
  12538 const formatTime = seconds => {
  12539  const minutes = Math.floor(seconds / 60).toString().padStart(2, "0");
  12540  const secs = (seconds % 60).toString().padStart(2, "0");
  12541  return `${minutes}:${secs}`;
  12542 };
  12543 
  12544 /**
  12545 * Validates that the inputs in the timer only allow numerical digits (0-9)
  12546 *
  12547 * @param input - The character being input
  12548 * @returns boolean - true if valid numeric input, false otherwise
  12549 */
  12550 const isNumericValue = input => {
  12551  // Check for null/undefined input or non-numeric characters
  12552  return input && /^\d+$/.test(input);
  12553 };
  12554 
  12555 /**
  12556 * Validates if adding a new digit would exceed the 2-character limit
  12557 *
  12558 * @param currentValue - The current value in the field
  12559 * @returns boolean - true if at 2-character limit, false otherwise
  12560 */
  12561 const isAtMaxLength = currentValue => {
  12562  return currentValue.length >= 2;
  12563 };
  12564 
  12565 /**
  12566 * Converts a polar coordinate (angle on circle) into a percentage-based [x,y] position for clip-path
  12567 *
  12568 * @param cx
  12569 * @param cy
  12570 * @param radius
  12571 * @param angle
  12572 * @returns string
  12573 */
  12574 const polarToPercent = (cx, cy, radius, angle) => {
  12575  const rad = (angle - 90) * Math.PI / 180;
  12576  const x = cx + radius * Math.cos(rad);
  12577  const y = cy + radius * Math.sin(rad);
  12578  return `${x}% ${y}%`;
  12579 };
  12580 
  12581 /**
  12582 * Generates a clip-path polygon string that represents a pie slice from 0 degrees
  12583 * to the current progress angle
  12584 *
  12585 * @returns string
  12586 * @param progress
  12587 */
  12588 const getClipPath = progress => {
  12589  const cx = 50;
  12590  const cy = 50;
  12591  const radius = 50;
  12592  // Show some progress right at the start - 6 degrees is just enough to paint a dot once the timer is ticking
  12593  const angle = progress > 0 ? Math.max(progress * 360, 6) : 0;
  12594  const points = [`50% 50%`];
  12595  for (let a = 0; a <= angle; a += 2) {
  12596    points.push(polarToPercent(cx, cy, radius, a));
  12597  }
  12598  return `polygon(${points.join(", ")})`;
  12599 };
  12600 const FocusTimer = ({
  12601  dispatch,
  12602  handleUserInteraction,
  12603  isMaximized
  12604 }) => {
  12605  const [timeLeft, setTimeLeft] = (0,external_React_namespaceObject.useState)(0);
  12606  // calculated value for the progress circle; 1 = 100%
  12607  const [progress, setProgress] = (0,external_React_namespaceObject.useState)(0);
  12608  const activeMinutesRef = (0,external_React_namespaceObject.useRef)(null);
  12609  const activeSecondsRef = (0,external_React_namespaceObject.useRef)(null);
  12610  const arcRef = (0,external_React_namespaceObject.useRef)(null);
  12611  const timerType = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.TimerWidget.timerType);
  12612  const timerData = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.TimerWidget);
  12613  const {
  12614    duration,
  12615    initialDuration,
  12616    startTime,
  12617    isRunning
  12618  } = timerData[timerType];
  12619  const initialTimerDuration = timerData[timerType].initialDuration;
  12620  const handleTimerInteraction = (0,external_React_namespaceObject.useCallback)(() => handleUserInteraction("focusTimer"), [handleUserInteraction]);
  12621  const handleIntersection = (0,external_React_namespaceObject.useCallback)(() => {
  12622    dispatch(actionCreators.AlsoToMain({
  12623      type: actionTypes.WIDGETS_TIMER_USER_IMPRESSION
  12624    }));
  12625  }, [dispatch]);
  12626  const timerRef = useIntersectionObserver(handleIntersection);
  12627  const resetProgressCircle = (0,external_React_namespaceObject.useCallback)(() => {
  12628    if (arcRef?.current) {
  12629      arcRef.current.style.clipPath = "polygon(50% 50%)";
  12630      arcRef.current.style.webkitClipPath = "polygon(50% 50%)";
  12631    }
  12632    setProgress(0);
  12633    handleTimerInteraction();
  12634  }, [arcRef, handleTimerInteraction]);
  12635  const prefs = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.Prefs.values);
  12636  const showSystemNotifications = prefs["widgets.focusTimer.showSystemNotifications"];
  12637  (0,external_React_namespaceObject.useEffect)(() => {
  12638    // resets default values after timer ends
  12639    let interval;
  12640    let hasReachedZero = false;
  12641    if (isRunning && duration > 0) {
  12642      interval = setInterval(() => {
  12643        const currentTime = Math.floor(Date.now() / 1000);
  12644        const elapsed = currentTime - startTime;
  12645        const remaining = calculateTimeRemaining(duration, startTime);
  12646 
  12647        // using setTimeLeft to trigger a re-render of the component to show live countdown each second
  12648        setTimeLeft(remaining);
  12649        setProgress((initialDuration - remaining) / initialDuration);
  12650        if (elapsed >= duration && hasReachedZero) {
  12651          clearInterval(interval);
  12652          (0,external_ReactRedux_namespaceObject.batch)(() => {
  12653            dispatch(actionCreators.AlsoToMain({
  12654              type: actionTypes.WIDGETS_TIMER_END,
  12655              data: {
  12656                timerType,
  12657                duration: initialTimerDuration,
  12658                initialDuration: initialTimerDuration
  12659              }
  12660            }));
  12661            dispatch(actionCreators.OnlyToMain({
  12662              type: actionTypes.WIDGETS_TIMER_USER_EVENT,
  12663              data: {
  12664                userAction: FocusTimer_USER_ACTION_TYPES.TIMER_END
  12665              }
  12666            }));
  12667          });
  12668 
  12669          // animate the progress circle to turn solid green
  12670          setProgress(1);
  12671 
  12672          // More transitions after a delay to allow the animation above to complete
  12673          setTimeout(() => {
  12674            // progress circle goes back to default grey
  12675            resetProgressCircle();
  12676 
  12677            // There's more to see!
  12678            setTimeout(() => {
  12679              // switch over to the other timer type
  12680              // eslint-disable-next-line max-nested-callbacks
  12681              (0,external_ReactRedux_namespaceObject.batch)(() => {
  12682                dispatch(actionCreators.AlsoToMain({
  12683                  type: actionTypes.WIDGETS_TIMER_SET_TYPE,
  12684                  data: {
  12685                    timerType: timerType === "focus" ? "break" : "focus"
  12686                  }
  12687                }));
  12688                dispatch(actionCreators.OnlyToMain({
  12689                  type: actionTypes.WIDGETS_TIMER_USER_EVENT,
  12690                  data: {
  12691                    userAction: timerType === "focus" ? FocusTimer_USER_ACTION_TYPES.TIMER_TOGGLE_BREAK : FocusTimer_USER_ACTION_TYPES.TIMER_TOGGLE_FOCUS
  12692                  }
  12693                }));
  12694              });
  12695            }, 500);
  12696          }, 1000);
  12697        } else if (elapsed >= duration) {
  12698          hasReachedZero = true;
  12699        }
  12700      }, 1000);
  12701    }
  12702 
  12703    // Shows the correct live time in the UI whenever the timer state changes
  12704    const newTime = isRunning ? calculateTimeRemaining(duration, startTime) : duration;
  12705    setTimeLeft(newTime);
  12706 
  12707    // Set progress for paused timers (handles page load and timer type toggling)
  12708    if (!isRunning && duration < initialDuration) {
  12709      // Show previously elapsed time
  12710      setProgress((initialDuration - duration) / initialDuration);
  12711    } else if (!isRunning) {
  12712      // Reset progress for fresh timers
  12713      setProgress(0);
  12714    }
  12715    return () => clearInterval(interval);
  12716  }, [isRunning, startTime, duration, initialDuration, dispatch, resetProgressCircle, timerType, initialTimerDuration]);
  12717 
  12718  // Update the clip-path of the gradient circle to match the current progress value
  12719  (0,external_React_namespaceObject.useEffect)(() => {
  12720    if (arcRef?.current) {
  12721      // Only set clip-path if current timer has been started or is running
  12722      if (progress > 0 || isRunning) {
  12723        arcRef.current.style.clipPath = getClipPath(progress);
  12724      } else {
  12725        arcRef.current.style.clipPath = "";
  12726      }
  12727    }
  12728  }, [progress, isRunning]);
  12729 
  12730  // set timer function
  12731  const setTimerDuration = () => {
  12732    const minutesEl = activeMinutesRef.current;
  12733    const secondsEl = activeSecondsRef.current;
  12734    const minutesValue = minutesEl.innerText.trim() || "0";
  12735    const secondsValue = secondsEl.innerText.trim() || "0";
  12736    let minutes = parseInt(minutesValue || "0", 10);
  12737    let seconds = parseInt(secondsValue || "0", 10);
  12738 
  12739    // Set a limit of 99 minutes
  12740    minutes = Math.min(minutes, 99);
  12741    // Set a limit of 59 seconds
  12742    seconds = Math.min(seconds, 59);
  12743    const totalSeconds = minutes * 60 + seconds;
  12744    if (!Number.isNaN(totalSeconds) && totalSeconds > 0 && totalSeconds !== duration) {
  12745      (0,external_ReactRedux_namespaceObject.batch)(() => {
  12746        dispatch(actionCreators.AlsoToMain({
  12747          type: actionTypes.WIDGETS_TIMER_SET_DURATION,
  12748          data: {
  12749            timerType,
  12750            duration: totalSeconds
  12751          }
  12752        }));
  12753        dispatch(actionCreators.OnlyToMain({
  12754          type: actionTypes.WIDGETS_TIMER_USER_EVENT,
  12755          data: {
  12756            userAction: FocusTimer_USER_ACTION_TYPES.TIMER_SET
  12757          }
  12758        }));
  12759      });
  12760    }
  12761    handleTimerInteraction();
  12762  };
  12763 
  12764  // Pause timer function
  12765  const toggleTimer = () => {
  12766    if (!isRunning && duration > 0) {
  12767      (0,external_ReactRedux_namespaceObject.batch)(() => {
  12768        dispatch(actionCreators.AlsoToMain({
  12769          type: actionTypes.WIDGETS_TIMER_PLAY,
  12770          data: {
  12771            timerType
  12772          }
  12773        }));
  12774        dispatch(actionCreators.OnlyToMain({
  12775          type: actionTypes.WIDGETS_TIMER_USER_EVENT,
  12776          data: {
  12777            userAction: FocusTimer_USER_ACTION_TYPES.TIMER_PLAY
  12778          }
  12779        }));
  12780      });
  12781    } else if (isRunning) {
  12782      // calculated to get the new baseline of the timer when it starts or resumes
  12783      const remaining = calculateTimeRemaining(duration, startTime);
  12784      (0,external_ReactRedux_namespaceObject.batch)(() => {
  12785        dispatch(actionCreators.AlsoToMain({
  12786          type: actionTypes.WIDGETS_TIMER_PAUSE,
  12787          data: {
  12788            timerType,
  12789            duration: remaining
  12790          }
  12791        }));
  12792        dispatch(actionCreators.OnlyToMain({
  12793          type: actionTypes.WIDGETS_TIMER_USER_EVENT,
  12794          data: {
  12795            userAction: FocusTimer_USER_ACTION_TYPES.TIMER_PAUSE
  12796          }
  12797        }));
  12798      });
  12799    }
  12800    handleTimerInteraction();
  12801  };
  12802 
  12803  // reset timer function
  12804  const resetTimer = () => {
  12805    (0,external_ReactRedux_namespaceObject.batch)(() => {
  12806      dispatch(actionCreators.AlsoToMain({
  12807        type: actionTypes.WIDGETS_TIMER_RESET,
  12808        data: {
  12809          timerType,
  12810          duration: initialTimerDuration,
  12811          initialDuration: initialTimerDuration
  12812        }
  12813      }));
  12814      dispatch(actionCreators.OnlyToMain({
  12815        type: actionTypes.WIDGETS_TIMER_USER_EVENT,
  12816        data: {
  12817          userAction: FocusTimer_USER_ACTION_TYPES.TIMER_RESET
  12818        }
  12819      }));
  12820    });
  12821 
  12822    // Reset progress value and gradient arc on the progress circle
  12823    resetProgressCircle();
  12824    handleTimerInteraction();
  12825  };
  12826 
  12827  // Toggles between "focus" and "break" timer types
  12828  const toggleType = type => {
  12829    const oldTypeRemaining = calculateTimeRemaining(duration, startTime);
  12830    (0,external_ReactRedux_namespaceObject.batch)(() => {
  12831      // The type we are toggling away from automatically pauses
  12832      dispatch(actionCreators.AlsoToMain({
  12833        type: actionTypes.WIDGETS_TIMER_PAUSE,
  12834        data: {
  12835          timerType,
  12836          duration: oldTypeRemaining
  12837        }
  12838      }));
  12839      dispatch(actionCreators.OnlyToMain({
  12840        type: actionTypes.WIDGETS_TIMER_USER_EVENT,
  12841        data: {
  12842          userAction: FocusTimer_USER_ACTION_TYPES.TIMER_PAUSE
  12843        }
  12844      }));
  12845 
  12846      // Sets the current timer type so it persists when opening a new tab
  12847      dispatch(actionCreators.AlsoToMain({
  12848        type: actionTypes.WIDGETS_TIMER_SET_TYPE,
  12849        data: {
  12850          timerType: type
  12851        }
  12852      }));
  12853      dispatch(actionCreators.OnlyToMain({
  12854        type: actionTypes.WIDGETS_TIMER_USER_EVENT,
  12855        data: {
  12856          userAction: type === "focus" ? FocusTimer_USER_ACTION_TYPES.TIMER_TOGGLE_FOCUS : FocusTimer_USER_ACTION_TYPES.TIMER_TOGGLE_BREAK
  12857        }
  12858      }));
  12859    });
  12860    handleTimerInteraction();
  12861  };
  12862  const handleKeyDown = e => {
  12863    if (e.key === "Enter") {
  12864      e.preventDefault();
  12865      setTimerDuration(e);
  12866      handleTimerInteraction();
  12867    }
  12868    if (e.key === "Tab") {
  12869      setTimerDuration(e);
  12870      handleTimerInteraction();
  12871    }
  12872  };
  12873  const handleBeforeInput = e => {
  12874    const input = e.data;
  12875    const values = e.target.innerText.trim();
  12876 
  12877    // only allow numerical digits 0–9 for time input
  12878    if (!isNumericValue(input)) {
  12879      e.preventDefault();
  12880      return;
  12881    }
  12882    const selection = window.getSelection();
  12883    const selectedText = selection.toString();
  12884 
  12885    // if entire value is selected, replace it with the new input
  12886    if (selectedText === values) {
  12887      e.preventDefault(); // prevent default typing
  12888      e.target.innerText = input;
  12889 
  12890      // Places the caret at the end of the content-editable text
  12891      // This is a known problem with content-editable where the caret
  12892      const range = document.createRange();
  12893      range.selectNodeContents(e.target);
  12894      range.collapse(false);
  12895      const sel = window.getSelection();
  12896      sel.removeAllRanges();
  12897      sel.addRange(range);
  12898      return;
  12899    }
  12900 
  12901    // only allow 2 values each for minutes and seconds
  12902    if (isAtMaxLength(values)) {
  12903      e.preventDefault();
  12904    }
  12905  };
  12906  const handleFocus = e => {
  12907    if (isRunning) {
  12908      // calculated to get the new baseline of the timer when it starts or resumes
  12909      const remaining = calculateTimeRemaining(duration, startTime);
  12910      (0,external_ReactRedux_namespaceObject.batch)(() => {
  12911        dispatch(actionCreators.AlsoToMain({
  12912          type: actionTypes.WIDGETS_TIMER_PAUSE,
  12913          data: {
  12914            timerType,
  12915            duration: remaining
  12916          }
  12917        }));
  12918        dispatch(actionCreators.OnlyToMain({
  12919          type: actionTypes.WIDGETS_TIMER_USER_EVENT,
  12920          data: {
  12921            userAction: FocusTimer_USER_ACTION_TYPES.TIMER_PAUSE
  12922          }
  12923        }));
  12924      });
  12925    }
  12926 
  12927    // highlight entire text when focused on the time.
  12928    // this makes it easier to input the new time instead of backspacing
  12929    const el = e.target;
  12930    if (document.createRange && window.getSelection) {
  12931      const range = document.createRange();
  12932      range.selectNodeContents(el);
  12933      const sel = window.getSelection();
  12934      sel.removeAllRanges();
  12935      sel.addRange(range);
  12936    }
  12937  };
  12938  function handleLearnMore() {
  12939    dispatch(actionCreators.OnlyToMain({
  12940      type: actionTypes.OPEN_LINK,
  12941      data: {
  12942        url: "https://support.mozilla.org/kb/firefox-new-tab-widgets"
  12943      }
  12944    }));
  12945    handleTimerInteraction();
  12946  }
  12947  function handlePrefUpdate(prefName, prefValue) {
  12948    dispatch(actionCreators.OnlyToMain({
  12949      type: actionTypes.SET_PREF,
  12950      data: {
  12951        name: prefName,
  12952        value: prefValue
  12953      }
  12954    }));
  12955    handleTimerInteraction();
  12956  }
  12957  return timerData ? /*#__PURE__*/external_React_default().createElement("article", {
  12958    className: `focus-timer ${isMaximized ? "is-maximized" : ""}`,
  12959    ref: el => {
  12960      timerRef.current = [el];
  12961    }
  12962  }, /*#__PURE__*/external_React_default().createElement("div", {
  12963    className: "newtab-widget-timer-notification-title-wrapper"
  12964  }, /*#__PURE__*/external_React_default().createElement("h3", {
  12965    "data-l10n-id": "newtab-widget-timer-notification-title"
  12966  }), /*#__PURE__*/external_React_default().createElement("div", {
  12967    className: "focus-timer-context-menu-wrapper"
  12968  }, /*#__PURE__*/external_React_default().createElement("moz-button", {
  12969    className: "focus-timer-context-menu-button",
  12970    iconSrc: "chrome://global/skin/icons/more.svg",
  12971    menuId: "focus-timer-context-menu",
  12972    type: "ghost"
  12973  }), /*#__PURE__*/external_React_default().createElement("panel-list", {
  12974    id: "focus-timer-context-menu"
  12975  }, /*#__PURE__*/external_React_default().createElement("panel-item", {
  12976    "data-l10n-id": showSystemNotifications ? "newtab-widget-timer-menu-notifications" : "newtab-widget-timer-menu-notifications-on",
  12977    onClick: () => {
  12978      handlePrefUpdate("widgets.focusTimer.showSystemNotifications", !showSystemNotifications);
  12979    }
  12980  }), /*#__PURE__*/external_React_default().createElement("panel-item", {
  12981    "data-l10n-id": "newtab-widget-timer-menu-hide",
  12982    onClick: () => {
  12983      handlePrefUpdate("widgets.focusTimer.enabled", false);
  12984    }
  12985  }), /*#__PURE__*/external_React_default().createElement("panel-item", {
  12986    "data-l10n-id": "newtab-widget-timer-menu-learn-more",
  12987    onClick: handleLearnMore
  12988  })))), /*#__PURE__*/external_React_default().createElement("div", {
  12989    className: "focus-timer-tabs"
  12990  }, /*#__PURE__*/external_React_default().createElement("div", {
  12991    className: "focus-timer-tabs-buttons"
  12992  }, /*#__PURE__*/external_React_default().createElement("moz-button", {
  12993    type: timerType === "focus" ? "default" : "ghost",
  12994    "data-l10n-id": "newtab-widget-timer-mode-focus",
  12995    size: "small",
  12996    onClick: () => toggleType("focus")
  12997  }), /*#__PURE__*/external_React_default().createElement("moz-button", {
  12998    type: timerType === "break" ? "default" : "ghost",
  12999    "data-l10n-id": "newtab-widget-timer-mode-break",
  13000    size: "small",
  13001    onClick: () => toggleType("break")
  13002  }))), /*#__PURE__*/external_React_default().createElement("div", {
  13003    role: "progress",
  13004    className: `progress-circle-wrapper ${!showSystemNotifications && !timerData[timerType].isRunning ? "is-small" : ""}`
  13005  }, /*#__PURE__*/external_React_default().createElement("div", {
  13006    className: `progress-circle-background${timerType === "break" ? "-break" : ""}`
  13007  }), /*#__PURE__*/external_React_default().createElement("div", {
  13008    className: `progress-circle ${timerType === "focus" ? "focus-visible" : "focus-hidden"}`,
  13009    ref: timerType === "focus" ? arcRef : null
  13010  }), /*#__PURE__*/external_React_default().createElement("div", {
  13011    className: `progress-circle ${timerType === "break" ? "break-visible" : "break-hidden"}`,
  13012    ref: timerType === "break" ? arcRef : null
  13013  }), /*#__PURE__*/external_React_default().createElement("div", {
  13014    className: `progress-circle-complete${progress === 1 ? " visible" : ""}`
  13015  }), /*#__PURE__*/external_React_default().createElement("div", {
  13016    role: "timer",
  13017    className: "progress-circle-label"
  13018  }, /*#__PURE__*/external_React_default().createElement(EditableTimerFields, {
  13019    minutesRef: activeMinutesRef,
  13020    secondsRef: activeSecondsRef,
  13021    onKeyDown: handleKeyDown,
  13022    onBeforeInput: handleBeforeInput,
  13023    onFocus: handleFocus,
  13024    timeLeft: timeLeft,
  13025    onBlur: () => setTimerDuration()
  13026  }))), /*#__PURE__*/external_React_default().createElement("div", {
  13027    className: "set-timer-controls-wrapper"
  13028  }, /*#__PURE__*/external_React_default().createElement("div", {
  13029    className: `focus-timer-controls timer-running`
  13030  }, /*#__PURE__*/external_React_default().createElement("moz-button", FocusTimer_extends({}, !isRunning ? {
  13031    type: "primary"
  13032  } : {}, {
  13033    iconsrc: `chrome://global/skin/media/${isRunning ? "pause" : "play"}-fill.svg`,
  13034    "data-l10n-id": isRunning ? "newtab-widget-timer-label-pause" : "newtab-widget-timer-label-play",
  13035    onClick: toggleTimer
  13036  })), isRunning && /*#__PURE__*/external_React_default().createElement("moz-button", {
  13037    type: "icon ghost",
  13038    iconsrc: "chrome://newtab/content/data/content/assets/arrow-clockwise-16.svg",
  13039    "data-l10n-id": "newtab-widget-timer-reset",
  13040    onClick: resetTimer
  13041  }))), !showSystemNotifications && !timerData[timerType].isRunning && /*#__PURE__*/external_React_default().createElement("p", {
  13042    className: "timer-notification-status",
  13043    "data-l10n-id": "newtab-widget-timer-notification-warning"
  13044  })) : null;
  13045 };
  13046 function EditableTimerFields({
  13047  minutesRef,
  13048  secondsRef,
  13049  tabIndex = 0,
  13050  ...props
  13051 }) {
  13052  return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement("span", {
  13053    contentEditable: "true",
  13054    ref: minutesRef,
  13055    className: "timer-set-minutes",
  13056    onKeyDown: props.onKeyDown,
  13057    onBeforeInput: props.onBeforeInput,
  13058    onFocus: props.onFocus,
  13059    onBlur: props.onBlur,
  13060    tabIndex: tabIndex
  13061  }, formatTime(props.timeLeft).split(":")[0]), ":", /*#__PURE__*/external_React_default().createElement("span", {
  13062    contentEditable: "true",
  13063    ref: secondsRef,
  13064    className: "timer-set-seconds",
  13065    onKeyDown: props.onKeyDown,
  13066    onBeforeInput: props.onBeforeInput,
  13067    onFocus: props.onFocus,
  13068    onBlur: props.onBlur,
  13069    tabIndex: tabIndex
  13070  }, formatTime(props.timeLeft).split(":")[1]));
  13071 }
  13072 ;// CONCATENATED MODULE: ./content-src/components/Widgets/WeatherForecast/WeatherForecast.jsx
  13073 /* This Source Code Form is subject to the terms of the Mozilla Public
  13074 * License, v. 2.0. If a copy of the MPL was not distributed with this
  13075 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
  13076 
  13077 
  13078 function WeatherForecast() {
  13079  const prefs = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.Prefs.values);
  13080  const weatherData = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.Weather);
  13081  const WEATHER_SUGGESTION = weatherData.suggestions?.[0];
  13082  const showDetailedView = prefs["weather.display"] === "detailed";
  13083  if (!showDetailedView || !weatherData?.initialized) {
  13084    return null;
  13085  }
  13086  return /*#__PURE__*/React.createElement("article", {
  13087    className: "weather-forecast-widget"
  13088  }, /*#__PURE__*/React.createElement("div", {
  13089    className: "city-wrapper"
  13090  }, /*#__PURE__*/React.createElement("h3", null, weatherData.locationData.city)), /*#__PURE__*/React.createElement("div", {
  13091    className: "current-weather-wrapper"
  13092  }, /*#__PURE__*/React.createElement("div", {
  13093    className: "weather-icon-column"
  13094  }, /*#__PURE__*/React.createElement("span", {
  13095    className: `weather-icon iconId${WEATHER_SUGGESTION.current_conditions.icon_id}`
  13096  })), /*#__PURE__*/React.createElement("div", {
  13097    className: "weather-info-column"
  13098  }, /*#__PURE__*/React.createElement("span", {
  13099    className: "temperature-unit"
  13100  }, WEATHER_SUGGESTION.current_conditions.temperature[prefs["weather.temperatureUnits"]], "\xB0", prefs["weather.temperatureUnits"]), /*#__PURE__*/React.createElement("span", {
  13101    className: "temperature-description"
  13102  }, WEATHER_SUGGESTION.current_conditions.summary)), /*#__PURE__*/React.createElement("div", {
  13103    className: "high-low-column"
  13104  }, /*#__PURE__*/React.createElement("span", {
  13105    className: "high-temperature"
  13106  }, /*#__PURE__*/React.createElement("span", {
  13107    className: "arrow-icon arrow-up"
  13108  }), WEATHER_SUGGESTION.forecast.high[prefs["weather.temperatureUnits"]], "\xB0"), /*#__PURE__*/React.createElement("span", {
  13109    className: "low-temperature"
  13110  }, /*#__PURE__*/React.createElement("span", {
  13111    className: "arrow-icon arrow-down"
  13112  }), WEATHER_SUGGESTION.forecast.low[prefs["weather.temperatureUnits"]], "\xB0"))), /*#__PURE__*/React.createElement("hr", null), /*#__PURE__*/React.createElement("div", {
  13113    className: "forecast-row"
  13114  }, /*#__PURE__*/React.createElement("p", {
  13115    className: "today-forecast",
  13116    "data-l10n-id": "newtab-weather-todays-forecast"
  13117  }), /*#__PURE__*/React.createElement("ul", {
  13118    className: "forecast-row-items"
  13119  }, /*#__PURE__*/React.createElement("li", null, /*#__PURE__*/React.createElement("span", null, "80\xB0"), /*#__PURE__*/React.createElement("span", {
  13120    className: `weather-icon iconId${WEATHER_SUGGESTION.current_conditions.icon_id}`
  13121  }), /*#__PURE__*/React.createElement("span", null, "7:00")), /*#__PURE__*/React.createElement("li", null, /*#__PURE__*/React.createElement("span", null, "80\xB0"), /*#__PURE__*/React.createElement("span", {
  13122    className: `weather-icon iconId${WEATHER_SUGGESTION.current_conditions.icon_id}`
  13123  }), /*#__PURE__*/React.createElement("span", null, "7:00")), /*#__PURE__*/React.createElement("li", null, /*#__PURE__*/React.createElement("span", null, "80\xB0"), /*#__PURE__*/React.createElement("span", {
  13124    className: `weather-icon iconId${WEATHER_SUGGESTION.current_conditions.icon_id}`
  13125  }), /*#__PURE__*/React.createElement("span", null, "7:00")), /*#__PURE__*/React.createElement("li", null, /*#__PURE__*/React.createElement("span", null, "80\xB0"), /*#__PURE__*/React.createElement("span", {
  13126    className: `weather-icon iconId${WEATHER_SUGGESTION.current_conditions.icon_id}`
  13127  }), /*#__PURE__*/React.createElement("span", null, "7:00")), /*#__PURE__*/React.createElement("li", null, /*#__PURE__*/React.createElement("span", null, "80\xB0"), /*#__PURE__*/React.createElement("span", {
  13128    className: `weather-icon iconId${WEATHER_SUGGESTION.current_conditions.icon_id}`
  13129  }), /*#__PURE__*/React.createElement("span", null, "7:00")))), /*#__PURE__*/React.createElement("div", {
  13130    className: "weather-forecast-footer"
  13131  }, /*#__PURE__*/React.createElement("a", {
  13132    href: "#",
  13133    className: "full-forecast",
  13134    "data-l10n-id": "newtab-weather-see-full-forecast"
  13135  }), /*#__PURE__*/React.createElement("span", {
  13136    className: "sponsored-text",
  13137    "data-l10n-id": "newtab-weather-sponsored",
  13138    "data-l10n-args": "{\"provider\": \"AccuWeather\xAE\"}"
  13139  })));
  13140 }
  13141 
  13142 ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/FeatureHighlight/WidgetsFeatureHighlight.jsx
  13143 /* This Source Code Form is subject to the terms of the Mozilla Public
  13144 * License, v. 2.0. If a copy of the MPL was not distributed with this
  13145 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
  13146 
  13147 
  13148 
  13149 function WidgetsFeatureHighlight({
  13150  handleDismiss,
  13151  handleBlock,
  13152  dispatch
  13153 }) {
  13154  const {
  13155    messageData
  13156  } = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.Messages);
  13157  return /*#__PURE__*/React.createElement(FeatureHighlight, {
  13158    position: "inset-inline-center inset-block-end",
  13159    arrowPosition: "arrow-top-center",
  13160    openedOverride: true,
  13161    showButtonIcon: false,
  13162    feature: messageData?.content?.feature,
  13163    modalClassName: "widget-highlight-wrapper",
  13164    message: /*#__PURE__*/React.createElement("div", {
  13165      className: "widget-highlight"
  13166    }, /*#__PURE__*/React.createElement("img", {
  13167      src: "chrome://newtab/content/data/content/assets/widget-message.png",
  13168      alt: ""
  13169    }), /*#__PURE__*/React.createElement("h3", {
  13170      "data-l10n-id": "newtab-widget-message-title"
  13171    }), /*#__PURE__*/React.createElement("p", {
  13172      "data-l10n-id": "newtab-widget-message-copy"
  13173    })),
  13174    dispatch: dispatch,
  13175    dismissCallback: () => {
  13176      handleDismiss();
  13177      handleBlock();
  13178    },
  13179    outsideClickCallback: handleDismiss
  13180  });
  13181 }
  13182 
  13183 ;// CONCATENATED MODULE: ./content-src/components/Widgets/Widgets.jsx
  13184 /* This Source Code Form is subject to the terms of the Mozilla Public
  13185 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  13186 * You can obtain one at http://mozilla.org/MPL/2.0/. */
  13187 
  13188 
  13189 
  13190 
  13191 
  13192 
  13193 
  13194 
  13195 
  13196 const PREF_WIDGETS_LISTS_ENABLED = "widgets.lists.enabled";
  13197 const PREF_WIDGETS_SYSTEM_LISTS_ENABLED = "widgets.system.lists.enabled";
  13198 const PREF_WIDGETS_TIMER_ENABLED = "widgets.focusTimer.enabled";
  13199 const PREF_WIDGETS_SYSTEM_TIMER_ENABLED = "widgets.system.focusTimer.enabled";
  13200 const PREF_WIDGETS_WEATHER_FORECAST_ENABLED = "widgets.weatherForecast.enabled";
  13201 const PREF_WIDGETS_SYSTEM_WEATHER_FORECAST_ENABLED = "widgets.system.weatherForecast.enabled";
  13202 const PREF_WIDGETS_MAXIMIZED = "widgets.maximized";
  13203 const PREF_WIDGETS_SYSTEM_MAXIMIZED = "widgets.system.maximized";
  13204 
  13205 // resets timer to default values (exported for testing)
  13206 // In practice, this logic runs inside a useEffect when
  13207 // the timer widget is disabled (after the pref flips from true to false).
  13208 // Because Enzyme tests cannot reliably simulate that pref update or trigger
  13209 // the related useEffect, we expose this helper to at least just test the reset behavior instead
  13210 
  13211 function resetTimerToDefaults(dispatch, timerType) {
  13212  const originalTime = timerType === "focus" ? 1500 : 300;
  13213 
  13214  // Reset both focus and break timers to their initial durations
  13215  dispatch(actionCreators.AlsoToMain({
  13216    type: actionTypes.WIDGETS_TIMER_RESET,
  13217    data: {
  13218      timerType,
  13219      duration: originalTime,
  13220      initialDuration: originalTime
  13221    }
  13222  }));
  13223 
  13224  // Set the timer type back to "focus"
  13225  dispatch(actionCreators.AlsoToMain({
  13226    type: actionTypes.WIDGETS_TIMER_SET_TYPE,
  13227    data: {
  13228      timerType: "focus"
  13229    }
  13230  }));
  13231 }
  13232 function Widgets() {
  13233  const prefs = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.Prefs.values);
  13234  const {
  13235    messageData
  13236  } = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.Messages);
  13237  const timerType = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.TimerWidget.timerType);
  13238  const timerData = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.TimerWidget);
  13239  const isMaximized = prefs[PREF_WIDGETS_MAXIMIZED];
  13240  const dispatch = (0,external_ReactRedux_namespaceObject.useDispatch)();
  13241  const nimbusListsEnabled = prefs.widgetsConfig?.listsEnabled;
  13242  const nimbusTimerEnabled = prefs.widgetsConfig?.timerEnabled;
  13243  const nimbusWeatherForecastEnabled = prefs.widgetsConfig?.weatherForecastEnabled;
  13244  const nimbusListsTrainhopEnabled = prefs.trainhopConfig?.widgets?.listsEnabled;
  13245  const nimbusTimerTrainhopEnabled = prefs.trainhopConfig?.widgets?.timerEnabled;
  13246  const nimbusWeatherForecastTrainhopEnabled = prefs.trainhopConfig?.widgets?.weatherForecastEnabled;
  13247  const listsEnabled = (nimbusListsTrainhopEnabled || nimbusListsEnabled || prefs[PREF_WIDGETS_SYSTEM_LISTS_ENABLED]) && prefs[PREF_WIDGETS_LISTS_ENABLED];
  13248  const timerEnabled = (nimbusTimerTrainhopEnabled || nimbusTimerEnabled || prefs[PREF_WIDGETS_SYSTEM_TIMER_ENABLED]) && prefs[PREF_WIDGETS_TIMER_ENABLED];
  13249  const weatherForecastEnabled = (nimbusWeatherForecastTrainhopEnabled || nimbusWeatherForecastEnabled || prefs[PREF_WIDGETS_SYSTEM_WEATHER_FORECAST_ENABLED]) && prefs[PREF_WIDGETS_WEATHER_FORECAST_ENABLED];
  13250 
  13251  // track previous timerEnabled state to detect when it becomes disabled
  13252  const prevTimerEnabledRef = (0,external_React_namespaceObject.useRef)(timerEnabled);
  13253 
  13254  // Reset timer when it becomes disabled
  13255  (0,external_React_namespaceObject.useEffect)(() => {
  13256    const wasTimerEnabled = prevTimerEnabledRef.current;
  13257    const isTimerEnabled = timerEnabled;
  13258 
  13259    // Only reset if timer was enabled and is now disabled
  13260    if (wasTimerEnabled && !isTimerEnabled && timerData) {
  13261      resetTimerToDefaults(dispatch, timerType);
  13262    }
  13263 
  13264    // Update the ref to track current state
  13265    prevTimerEnabledRef.current = isTimerEnabled;
  13266  }, [timerEnabled, timerData, dispatch, timerType]);
  13267 
  13268  // Sends a dispatch to disable all widgets
  13269  function handleHideAllWidgetsClick(e) {
  13270    e.preventDefault();
  13271    (0,external_ReactRedux_namespaceObject.batch)(() => {
  13272      dispatch(actionCreators.SetPref(PREF_WIDGETS_LISTS_ENABLED, false));
  13273      dispatch(actionCreators.SetPref(PREF_WIDGETS_TIMER_ENABLED, false));
  13274    });
  13275  }
  13276  function handleHideAllWidgetsKeyDown(e) {
  13277    if (e.key === "Enter" || e.key === " ") {
  13278      e.preventDefault();
  13279      (0,external_ReactRedux_namespaceObject.batch)(() => {
  13280        dispatch(actionCreators.SetPref(PREF_WIDGETS_LISTS_ENABLED, false));
  13281        dispatch(actionCreators.SetPref(PREF_WIDGETS_TIMER_ENABLED, false));
  13282      });
  13283    }
  13284  }
  13285 
  13286  // Toggles the maximized state of widgets
  13287  function handleToggleMaximizeClick(e) {
  13288    e.preventDefault();
  13289    dispatch(actionCreators.SetPref(PREF_WIDGETS_MAXIMIZED, !isMaximized));
  13290  }
  13291  function handleToggleMaximizeKeyDown(e) {
  13292    if (e.key === "Enter" || e.key === " ") {
  13293      e.preventDefault();
  13294      dispatch(actionCreators.SetPref(PREF_WIDGETS_MAXIMIZED, !isMaximized));
  13295    }
  13296  }
  13297  function handleUserInteraction(widgetName) {
  13298    const prefName = `widgets.${widgetName}.interaction`;
  13299    const hasInteracted = prefs[prefName];
  13300    // we want to make sure that the value is a strict false (and that the property exists)
  13301    if (hasInteracted === false) {
  13302      dispatch(actionCreators.SetPref(prefName, true));
  13303    }
  13304  }
  13305  if (!(listsEnabled || timerEnabled || weatherForecastEnabled)) {
  13306    return null;
  13307  }
  13308  return /*#__PURE__*/external_React_default().createElement("div", {
  13309    className: "widgets-wrapper"
  13310  }, /*#__PURE__*/external_React_default().createElement("div", {
  13311    className: "widgets-section-container"
  13312  }, /*#__PURE__*/external_React_default().createElement("div", {
  13313    className: "widgets-title-container"
  13314  }, /*#__PURE__*/external_React_default().createElement("h1", {
  13315    "data-l10n-id": "newtab-widget-section-title"
  13316  }), prefs[PREF_WIDGETS_SYSTEM_MAXIMIZED] && /*#__PURE__*/external_React_default().createElement("moz-button", {
  13317    id: "toggle-widgets-size-button",
  13318    type: "icon ghost",
  13319    size: "small"
  13320    // Toggle the icon and hover text
  13321    ,
  13322    "data-l10n-id": isMaximized ? "newtab-widget-section-maximize" : "newtab-widget-section-minimize",
  13323    iconsrc: `chrome://browser/skin/${isMaximized ? "fullscreen" : "fullscreen-exit"}.svg`,
  13324    onClick: handleToggleMaximizeClick,
  13325    onKeyDown: handleToggleMaximizeKeyDown
  13326  }), /*#__PURE__*/external_React_default().createElement("moz-button", {
  13327    id: "hide-all-widgets-button",
  13328    type: "icon ghost",
  13329    size: "small",
  13330    "data-l10n-id": "newtab-widget-section-hide-all-button",
  13331    iconsrc: "chrome://global/skin/icons/close.svg",
  13332    onClick: handleHideAllWidgetsClick,
  13333    onKeyDown: handleHideAllWidgetsKeyDown
  13334  })), /*#__PURE__*/external_React_default().createElement("div", {
  13335    className: `widgets-container ${isMaximized ? "is-maximized" : ""}`
  13336  }, listsEnabled && /*#__PURE__*/external_React_default().createElement(Lists, {
  13337    dispatch: dispatch,
  13338    handleUserInteraction: handleUserInteraction,
  13339    isMaximized: isMaximized
  13340  }), timerEnabled && /*#__PURE__*/external_React_default().createElement(FocusTimer, {
  13341    dispatch: dispatch,
  13342    handleUserInteraction: handleUserInteraction,
  13343    isMaximized: isMaximized
  13344  }), weatherForecastEnabled && /*#__PURE__*/external_React_default().createElement(WeatherForecast, {
  13345    dispatch: dispatch,
  13346    handleUserInteraction: handleUserInteraction,
  13347    isMaximized: isMaximized
  13348  }))), messageData?.content?.messageType === "WidgetMessage" && /*#__PURE__*/external_React_default().createElement(MessageWrapper, {
  13349    dispatch: dispatch
  13350  }, /*#__PURE__*/external_React_default().createElement(WidgetsFeatureHighlight, {
  13351    dispatch: dispatch
  13352  })));
  13353 }
  13354 
  13355 ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamBase/DiscoveryStreamBase.jsx
  13356 /* This Source Code Form is subject to the terms of the Mozilla Public
  13357 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  13358 * You can obtain one at http://mozilla.org/MPL/2.0/. */
  13359 
  13360 
  13361 
  13362 
  13363 
  13364 
  13365 
  13366 
  13367 
  13368 
  13369 
  13370 
  13371 
  13372 
  13373 
  13374 
  13375 const ALLOWED_CSS_URL_PREFIXES = ["chrome://", "resource://", "https://img-getpocket.cdn.mozilla.net/"];
  13376 const DUMMY_CSS_SELECTOR = "DUMMY#CSS.SELECTOR";
  13377 
  13378 /**
  13379 * Validate a CSS declaration. The values are assumed to be normalized by CSSOM.
  13380 */
  13381 function isAllowedCSS(property, value) {
  13382  // Bug 1454823: INTERNAL properties, e.g., -moz-context-properties, are
  13383  // exposed but their values aren't resulting in getting nothing. Fortunately,
  13384  // we don't care about validating the values of the current set of properties.
  13385  if (value === undefined) {
  13386    return true;
  13387  }
  13388 
  13389  // Make sure all urls are of the allowed protocols/prefixes
  13390  const urls = value.match(/url\("[^"]+"\)/g);
  13391  return !urls || urls.every(url => ALLOWED_CSS_URL_PREFIXES.some(prefix => url.slice(5).startsWith(prefix)));
  13392 }
  13393 class _DiscoveryStreamBase extends (external_React_default()).PureComponent {
  13394  constructor(props) {
  13395    super(props);
  13396    this.onStyleMount = this.onStyleMount.bind(this);
  13397  }
  13398  onStyleMount(style) {
  13399    // Unmounting style gets rid of old styles, so nothing else to do
  13400    if (!style) {
  13401      return;
  13402    }
  13403    const {
  13404      sheet
  13405    } = style;
  13406    const styles = JSON.parse(style.dataset.styles);
  13407    styles.forEach((row, rowIndex) => {
  13408      row.forEach((component, componentIndex) => {
  13409        // Nothing to do without optional styles overrides
  13410        if (!component) {
  13411          return;
  13412        }
  13413        Object.entries(component).forEach(([selectors, declarations]) => {
  13414          // Start with a dummy rule to validate declarations and selectors
  13415          sheet.insertRule(`${DUMMY_CSS_SELECTOR} {}`);
  13416          const [rule] = sheet.cssRules;
  13417 
  13418          // Validate declarations and remove any offenders. CSSOM silently
  13419          // discards invalid entries, so here we apply extra restrictions.
  13420          rule.style = declarations;
  13421          [...rule.style].forEach(property => {
  13422            const value = rule.style[property];
  13423            if (!isAllowedCSS(property, value)) {
  13424              console.error(`Bad CSS declaration ${property}: ${value}`);
  13425              rule.style.removeProperty(property);
  13426            }
  13427          });
  13428 
  13429          // Set the actual desired selectors scoped to the component
  13430          const prefix = `.ds-layout > .ds-column:nth-child(${rowIndex + 1}) .ds-column-grid > :nth-child(${componentIndex + 1})`;
  13431          // NB: Splitting on "," doesn't work with strings with commas, but
  13432          // we're okay with not supporting those selectors
  13433          rule.selectorText = selectors.split(",").map(selector => prefix + (
  13434          // Assume :pseudo-classes are for component instead of descendant
  13435          selector[0] === ":" ? "" : " ") + selector).join(",");
  13436 
  13437          // CSSOM silently ignores bad selectors, so we'll be noisy instead
  13438          if (rule.selectorText === DUMMY_CSS_SELECTOR) {
  13439            console.error(`Bad CSS selector ${selectors}`);
  13440          }
  13441        });
  13442      });
  13443    });
  13444  }
  13445  renderComponent(component) {
  13446    switch (component.type) {
  13447      case "Highlights":
  13448        return /*#__PURE__*/external_React_default().createElement(Highlights, null);
  13449      case "TopSites":
  13450        return /*#__PURE__*/external_React_default().createElement("div", {
  13451          className: "ds-top-sites"
  13452        }, /*#__PURE__*/external_React_default().createElement(TopSites_TopSites, {
  13453          isFixed: true,
  13454          title: component.header?.title
  13455        }));
  13456      case "Message":
  13457        return /*#__PURE__*/external_React_default().createElement(DSMessage, {
  13458          title: component.header && component.header.title,
  13459          subtitle: component.header && component.header.subtitle,
  13460          link_text: component.header && component.header.link_text,
  13461          link_url: component.header && component.header.link_url,
  13462          icon: component.header && component.header.icon
  13463        });
  13464      case "SectionTitle":
  13465        return /*#__PURE__*/external_React_default().createElement(SectionTitle, {
  13466          header: component.header
  13467        });
  13468      case "Navigation":
  13469        return /*#__PURE__*/external_React_default().createElement(Navigation, {
  13470          dispatch: this.props.dispatch,
  13471          links: component.properties.links,
  13472          extraLinks: component.properties.extraLinks,
  13473          alignment: component.properties.alignment,
  13474          explore_topics: component.properties.explore_topics,
  13475          header: component.header,
  13476          locale: this.props.App.locale,
  13477          newFooterSection: component.newFooterSection,
  13478          privacyNoticeURL: component.properties.privacyNoticeURL
  13479        });
  13480      case "CardGrid":
  13481        {
  13482          const sectionsEnabled = this.props.Prefs.values["discoverystream.sections.enabled"];
  13483          if (sectionsEnabled) {
  13484            return /*#__PURE__*/external_React_default().createElement(CardSections, {
  13485              feed: component.feed,
  13486              data: component.data,
  13487              dispatch: this.props.dispatch,
  13488              type: component.type,
  13489              firstVisibleTimestamp: this.props.firstVisibleTimestamp,
  13490              ctaButtonSponsors: component.properties.ctaButtonSponsors,
  13491              ctaButtonVariant: component.properties.ctaButtonVariant,
  13492              placeholder: this.props.placeholder
  13493            });
  13494          }
  13495          return /*#__PURE__*/external_React_default().createElement(CardGrid, {
  13496            title: component.header && component.header.title,
  13497            data: component.data,
  13498            feed: component.feed,
  13499            widgets: component.widgets,
  13500            type: component.type,
  13501            dispatch: this.props.dispatch,
  13502            items: component.properties.items,
  13503            hybridLayout: component.properties.hybridLayout,
  13504            hideCardBackground: component.properties.hideCardBackground,
  13505            fourCardLayout: component.properties.fourCardLayout,
  13506            compactGrid: component.properties.compactGrid,
  13507            ctaButtonSponsors: component.properties.ctaButtonSponsors,
  13508            ctaButtonVariant: component.properties.ctaButtonVariant,
  13509            hideDescriptions: this.props.DiscoveryStream.hideDescriptions,
  13510            firstVisibleTimestamp: this.props.firstVisibleTimestamp,
  13511            spocPositions: component.spocs?.positions,
  13512            placeholder: this.props.placeholder
  13513          });
  13514        }
  13515      case "HorizontalRule":
  13516        return /*#__PURE__*/external_React_default().createElement(HorizontalRule, null);
  13517      case "PrivacyLink":
  13518        return /*#__PURE__*/external_React_default().createElement(PrivacyLink, {
  13519          properties: component.properties
  13520        });
  13521      case "Widgets":
  13522        return /*#__PURE__*/external_React_default().createElement(Widgets, null);
  13523      default:
  13524        return /*#__PURE__*/external_React_default().createElement("div", null, component.type);
  13525    }
  13526  }
  13527  renderStyles(styles) {
  13528    // Use json string as both the key and styles to render so React knows when
  13529    // to unmount and mount a new instance for new styles.
  13530    const json = JSON.stringify(styles);
  13531    return /*#__PURE__*/external_React_default().createElement("style", {
  13532      key: json,
  13533      "data-styles": json,
  13534      ref: this.onStyleMount
  13535    });
  13536  }
  13537  render() {
  13538    const {
  13539      locale
  13540    } = this.props;
  13541    // Bug 1980459 - Note that selectLayoutRender acts as a selector that transforms layout data based on current
  13542    // preferences and experiment flags. It runs after Redux state is populated but before render.
  13543    // Components removed in selectLayoutRender (e.g., Widgets or TopSites) will not appear in the
  13544    // layoutRender result, and therefore will not be rendered here regardless of logic below.
  13545 
  13546    // Select layout renders data by adding spocs and position to recommendations
  13547    const {
  13548      layoutRender
  13549    } = selectLayoutRender({
  13550      state: this.props.DiscoveryStream,
  13551      prefs: this.props.Prefs.values,
  13552      locale
  13553    });
  13554    const sectionsEnabled = this.props.Prefs.values["discoverystream.sections.enabled"];
  13555    const {
  13556      config
  13557    } = this.props.DiscoveryStream;
  13558    const topicSelectionEnabled = this.props.Prefs.values["discoverystream.topicSelection.enabled"];
  13559    const reportAdsEnabled = this.props.Prefs.values["discoverystream.reportAds.enabled"];
  13560    const spocsEnabled = this.props.Prefs.values["unifiedAds.spocs.enabled"];
  13561 
  13562    // Allow rendering without extracting special components
  13563    if (!config.collapsible) {
  13564      return this.renderLayout(layoutRender);
  13565    }
  13566 
  13567    // Find the first component of a type and remove it from layout
  13568    const extractComponent = type => {
  13569      for (const [rowIndex, row] of Object.entries(layoutRender)) {
  13570        for (const [index, component] of Object.entries(row.components)) {
  13571          if (component.type === type) {
  13572            // Remove the row if it was the only component or the single item
  13573            if (row.components.length === 1) {
  13574              layoutRender.splice(rowIndex, 1);
  13575            } else {
  13576              row.components.splice(index, 1);
  13577            }
  13578            return component;
  13579          }
  13580        }
  13581      }
  13582      return null;
  13583    };
  13584 
  13585    // Get "topstories" Section state for default values
  13586    const topStories = this.props.Sections.find(s => s.id === "topstories");
  13587    if (!topStories) {
  13588      return null;
  13589    }
  13590 
  13591    // Extract TopSites to render before the rest and Message to use for header
  13592    const topSites = extractComponent("TopSites");
  13593 
  13594    // There are two ways to enable widgets:
  13595    // Via `widgets.system.*` prefs or Nimbus experiment
  13596    const widgetsNimbusTrainhopEnabled = this.props.Prefs.values.trainhopConfig?.widgets?.enabled;
  13597    const widgetsNimbusEnabled = this.props.Prefs.values.widgetsConfig?.enabled;
  13598    const widgetsSystemPrefsEnabled = this.props.Prefs.values["widgets.system.enabled"];
  13599    const widgets = widgetsNimbusTrainhopEnabled || widgetsNimbusEnabled || widgetsSystemPrefsEnabled;
  13600    const message = extractComponent("Message") || {
  13601      header: {
  13602        link_text: topStories.learnMore.link.message,
  13603        link_url: topStories.learnMore.link.href,
  13604        title: topStories.title
  13605      }
  13606    };
  13607    const privacyLinkComponent = extractComponent("PrivacyLink");
  13608    let learnMore = {
  13609      link: {
  13610        href: message.header.link_url,
  13611        message: message.header.link_text
  13612      }
  13613    };
  13614    let sectionTitle = message.header.title;
  13615    let subTitle = "";
  13616    const {
  13617      DiscoveryStream
  13618    } = this.props;
  13619    return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, (reportAdsEnabled && spocsEnabled || sectionsEnabled) && /*#__PURE__*/external_React_default().createElement(ReportContent, {
  13620      spocs: DiscoveryStream.spocs
  13621    }), topSites && this.renderLayout([{
  13622      width: 12,
  13623      components: [topSites],
  13624      sectionType: "topsites"
  13625    }]), widgets && this.renderLayout([{
  13626      width: 12,
  13627      components: [{
  13628        type: "Widgets"
  13629      }],
  13630      sectionType: "widgets"
  13631    }]), !!layoutRender.length && /*#__PURE__*/external_React_default().createElement(CollapsibleSection, {
  13632      className: "ds-layout",
  13633      collapsed: topStories.pref.collapsed,
  13634      dispatch: this.props.dispatch,
  13635      id: topStories.id,
  13636      isFixed: true,
  13637      learnMore: learnMore,
  13638      privacyNoticeURL: topStories.privacyNoticeURL,
  13639      showPrefName: topStories.pref.feed,
  13640      title: sectionTitle,
  13641      subTitle: subTitle,
  13642      mayHaveTopicsSelection: topicSelectionEnabled,
  13643      sectionsEnabled: sectionsEnabled,
  13644      eventSource: "CARDGRID"
  13645    }, this.renderLayout(layoutRender)), this.renderLayout([{
  13646      width: 12,
  13647      components: [{
  13648        type: "Highlights"
  13649      }]
  13650    }]), privacyLinkComponent && this.renderLayout([{
  13651      width: 12,
  13652      components: [privacyLinkComponent]
  13653    }]));
  13654  }
  13655  renderLayout(layoutRender) {
  13656    const styles = [];
  13657    let [data] = layoutRender;
  13658    // Add helper class for topsites
  13659    const sectionClass = data.sectionType ? `ds-layout-${data.sectionType}` : "";
  13660    return /*#__PURE__*/external_React_default().createElement("div", {
  13661      className: `discovery-stream ds-layout ${sectionClass}`
  13662    }, layoutRender.map((row, rowIndex) => /*#__PURE__*/external_React_default().createElement("div", {
  13663      key: `row-${rowIndex}`,
  13664      className: `ds-column ds-column-${row.width}`
  13665    }, /*#__PURE__*/external_React_default().createElement("div", {
  13666      className: "ds-column-grid"
  13667    }, row.components.map((component, componentIndex) => {
  13668      if (!component) {
  13669        return null;
  13670      }
  13671      styles[rowIndex] = [...(styles[rowIndex] || []), component.styles];
  13672      return /*#__PURE__*/external_React_default().createElement("div", {
  13673        key: `component-${componentIndex}`
  13674      }, this.renderComponent(component, row.width));
  13675    })))), this.renderStyles(styles));
  13676  }
  13677 }
  13678 const DiscoveryStreamBase = (0,external_ReactRedux_namespaceObject.connect)(state => ({
  13679  DiscoveryStream: state.DiscoveryStream,
  13680  Prefs: state.Prefs,
  13681  Sections: state.Sections,
  13682  document: globalThis.document,
  13683  App: state.App
  13684 }))(_DiscoveryStreamBase);
  13685 ;// CONCATENATED MODULE: ./content-src/components/CustomizeMenu/SectionsMgmtPanel/SectionsMgmtPanel.jsx
  13686 function SectionsMgmtPanel_extends() { return SectionsMgmtPanel_extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, SectionsMgmtPanel_extends.apply(null, arguments); }
  13687 /* This Source Code Form is subject to the terms of the Mozilla Public
  13688 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  13689 * You can obtain one at http://mozilla.org/MPL/2.0/. */
  13690 
  13691 
  13692 
  13693 
  13694 // eslint-disable-next-line no-shadow
  13695 
  13696 function SectionsMgmtPanel({
  13697  exitEventFired,
  13698  pocketEnabled,
  13699  onSubpanelToggle,
  13700  togglePanel,
  13701  showPanel
  13702 }) {
  13703  const arrowButtonRef = (0,external_React_namespaceObject.useRef)(null);
  13704  const {
  13705    sectionPersonalization
  13706  } = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.DiscoveryStream);
  13707  const layoutComponents = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.DiscoveryStream.layout[0].components);
  13708  const sections = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.DiscoveryStream.feeds.data);
  13709  const dispatch = (0,external_ReactRedux_namespaceObject.useDispatch)();
  13710 
  13711  // TODO: Wrap sectionsFeedName -> sectionsList logic in try...catch?
  13712  let sectionsFeedName;
  13713  const cardGridEntry = layoutComponents.find(item => item.type === "CardGrid");
  13714  if (cardGridEntry) {
  13715    sectionsFeedName = cardGridEntry.feed.url;
  13716  }
  13717  let sectionsList;
  13718  if (sectionsFeedName) {
  13719    sectionsList = sections[sectionsFeedName].data.sections;
  13720  }
  13721  const [sectionsState, setSectionState] = (0,external_React_namespaceObject.useState)(sectionPersonalization); // State management with useState
  13722 
  13723  let followedSectionsData = sectionsList.filter(item => sectionsState[item.sectionKey]?.isFollowed);
  13724  let blockedSectionsData = sectionsList.filter(item => sectionsState[item.sectionKey]?.isBlocked);
  13725  function updateCachedData() {
  13726    // Reset cached followed/blocked list data while panel is open
  13727    setSectionState(sectionPersonalization);
  13728    followedSectionsData = sectionsList.filter(item => sectionsState[item.sectionKey]?.isFollowed);
  13729    blockedSectionsData = sectionsList.filter(item => sectionsState[item.sectionKey]?.isBlocked);
  13730  }
  13731  const onFollowClick = (0,external_React_namespaceObject.useCallback)((sectionKey, receivedRank) => {
  13732    dispatch(actionCreators.AlsoToMain({
  13733      type: actionTypes.SECTION_PERSONALIZATION_SET,
  13734      data: {
  13735        ...sectionPersonalization,
  13736        [sectionKey]: {
  13737          isFollowed: true,
  13738          isBlocked: false,
  13739          followedAt: new Date().toISOString()
  13740        }
  13741      }
  13742    }));
  13743    // Telemetry Event Dispatch
  13744    dispatch(actionCreators.OnlyToMain({
  13745      type: "FOLLOW_SECTION",
  13746      data: {
  13747        section: sectionKey,
  13748        section_position: receivedRank,
  13749        event_source: "CUSTOMIZE_PANEL"
  13750      }
  13751    }));
  13752  }, [dispatch, sectionPersonalization]);
  13753  const onBlockClick = (0,external_React_namespaceObject.useCallback)((sectionKey, receivedRank) => {
  13754    dispatch(actionCreators.AlsoToMain({
  13755      type: actionTypes.SECTION_PERSONALIZATION_SET,
  13756      data: {
  13757        ...sectionPersonalization,
  13758        [sectionKey]: {
  13759          isFollowed: false,
  13760          isBlocked: true
  13761        }
  13762      }
  13763    }));
  13764 
  13765    // Telemetry Event Dispatch
  13766    dispatch(actionCreators.OnlyToMain({
  13767      type: "BLOCK_SECTION",
  13768      data: {
  13769        section: sectionKey,
  13770        section_position: receivedRank,
  13771        event_source: "CUSTOMIZE_PANEL"
  13772      }
  13773    }));
  13774  }, [dispatch, sectionPersonalization]);
  13775  const onUnblockClick = (0,external_React_namespaceObject.useCallback)((sectionKey, receivedRank) => {
  13776    const updatedSectionData = {
  13777      ...sectionPersonalization
  13778    };
  13779    delete updatedSectionData[sectionKey];
  13780    dispatch(actionCreators.AlsoToMain({
  13781      type: actionTypes.SECTION_PERSONALIZATION_SET,
  13782      data: updatedSectionData
  13783    }));
  13784    // Telemetry Event Dispatch
  13785    dispatch(actionCreators.OnlyToMain({
  13786      type: "UNBLOCK_SECTION",
  13787      data: {
  13788        section: sectionKey,
  13789        section_position: receivedRank,
  13790        event_source: "CUSTOMIZE_PANEL"
  13791      }
  13792    }));
  13793  }, [dispatch, sectionPersonalization]);
  13794  const onUnfollowClick = (0,external_React_namespaceObject.useCallback)((sectionKey, receivedRank) => {
  13795    const updatedSectionData = {
  13796      ...sectionPersonalization
  13797    };
  13798    delete updatedSectionData[sectionKey];
  13799    dispatch(actionCreators.AlsoToMain({
  13800      type: actionTypes.SECTION_PERSONALIZATION_SET,
  13801      data: updatedSectionData
  13802    }));
  13803    // Telemetry Event Dispatch
  13804    dispatch(actionCreators.OnlyToMain({
  13805      type: "UNFOLLOW_SECTION",
  13806      data: {
  13807        section: sectionKey,
  13808        section_position: receivedRank,
  13809        event_source: "CUSTOMIZE_PANEL"
  13810      }
  13811    }));
  13812  }, [dispatch, sectionPersonalization]);
  13813 
  13814  // Close followed/blocked topic subpanel when parent menu is closed
  13815  (0,external_React_namespaceObject.useEffect)(() => {
  13816    if (exitEventFired && showPanel) {
  13817      togglePanel();
  13818    }
  13819  }, [exitEventFired, showPanel, togglePanel]);
  13820 
  13821  // Notify parent menu when subpanel opens/closes
  13822  (0,external_React_namespaceObject.useEffect)(() => {
  13823    if (onSubpanelToggle) {
  13824      onSubpanelToggle(showPanel);
  13825    }
  13826  }, [showPanel, onSubpanelToggle]);
  13827  (0,external_React_namespaceObject.useEffect)(() => {
  13828    if (showPanel) {
  13829      updateCachedData();
  13830    }
  13831    // eslint-disable-next-line react-hooks/exhaustive-deps
  13832  }, [showPanel]);
  13833  const handlePanelEntered = () => {
  13834    arrowButtonRef.current?.focus();
  13835  };
  13836  const followedSectionsList = followedSectionsData.map(({
  13837    sectionKey,
  13838    title,
  13839    receivedRank
  13840  }) => {
  13841    const following = sectionPersonalization[sectionKey]?.isFollowed;
  13842    return /*#__PURE__*/external_React_default().createElement("li", {
  13843      key: sectionKey
  13844    }, /*#__PURE__*/external_React_default().createElement("label", {
  13845      htmlFor: `follow-topic-${sectionKey}`
  13846    }, title), /*#__PURE__*/external_React_default().createElement("div", {
  13847      className: following ? "section-follow following" : "section-follow"
  13848    }, /*#__PURE__*/external_React_default().createElement("moz-button", {
  13849      onClick: () => following ? onUnfollowClick(sectionKey, receivedRank) : onFollowClick(sectionKey, receivedRank),
  13850      type: "default",
  13851      index: receivedRank,
  13852      section: sectionKey,
  13853      id: `follow-topic-${sectionKey}`
  13854    }, /*#__PURE__*/external_React_default().createElement("span", {
  13855      className: "section-button-follow-text",
  13856      "data-l10n-id": "newtab-section-follow-button"
  13857    }), /*#__PURE__*/external_React_default().createElement("span", {
  13858      className: "section-button-following-text",
  13859      "data-l10n-id": "newtab-section-following-button"
  13860    }), /*#__PURE__*/external_React_default().createElement("span", {
  13861      className: "section-button-unfollow-text",
  13862      "data-l10n-id": "newtab-section-unfollow-button"
  13863    }))));
  13864  });
  13865  const blockedSectionsList = blockedSectionsData.map(({
  13866    sectionKey,
  13867    title,
  13868    receivedRank
  13869  }) => {
  13870    const blocked = sectionPersonalization[sectionKey]?.isBlocked;
  13871    return /*#__PURE__*/external_React_default().createElement("li", {
  13872      key: sectionKey
  13873    }, /*#__PURE__*/external_React_default().createElement("label", {
  13874      htmlFor: `blocked-topic-${sectionKey}`
  13875    }, title), /*#__PURE__*/external_React_default().createElement("div", {
  13876      className: blocked ? "section-block blocked" : "section-block"
  13877    }, /*#__PURE__*/external_React_default().createElement("moz-button", {
  13878      onClick: () => blocked ? onUnblockClick(sectionKey, receivedRank) : onBlockClick(sectionKey, receivedRank),
  13879      type: "default",
  13880      index: receivedRank,
  13881      section: sectionKey,
  13882      id: `blocked-topic-${sectionKey}`
  13883    }, /*#__PURE__*/external_React_default().createElement("span", {
  13884      className: "section-button-block-text",
  13885      "data-l10n-id": "newtab-section-block-button"
  13886    }), /*#__PURE__*/external_React_default().createElement("span", {
  13887      className: "section-button-blocked-text",
  13888      "data-l10n-id": "newtab-section-blocked-button"
  13889    }), /*#__PURE__*/external_React_default().createElement("span", {
  13890      className: "section-button-unblock-text",
  13891      "data-l10n-id": "newtab-section-unblock-button"
  13892    }))));
  13893  });
  13894  return /*#__PURE__*/external_React_default().createElement("div", null, /*#__PURE__*/external_React_default().createElement("moz-box-button", SectionsMgmtPanel_extends({
  13895    onClick: togglePanel,
  13896    "data-l10n-id": "newtab-section-manage-topics-button-v2"
  13897  }, !pocketEnabled ? {
  13898    disabled: true
  13899  } : {})), /*#__PURE__*/external_React_default().createElement(external_ReactTransitionGroup_namespaceObject.CSSTransition, {
  13900    in: showPanel,
  13901    timeout: 300,
  13902    classNames: "sections-mgmt-panel",
  13903    unmountOnExit: true,
  13904    onEntered: handlePanelEntered
  13905  }, /*#__PURE__*/external_React_default().createElement("div", {
  13906    className: "sections-mgmt-panel"
  13907  }, /*#__PURE__*/external_React_default().createElement("button", {
  13908    ref: arrowButtonRef,
  13909    className: "arrow-button",
  13910    onClick: togglePanel
  13911  }, /*#__PURE__*/external_React_default().createElement("h1", {
  13912    "data-l10n-id": "newtab-section-mangage-topics-title"
  13913  })), /*#__PURE__*/external_React_default().createElement("h3", {
  13914    "data-l10n-id": "newtab-section-mangage-topics-followed-topics"
  13915  }), followedSectionsData.length ? /*#__PURE__*/external_React_default().createElement("ul", {
  13916    className: "topic-list"
  13917  }, followedSectionsList) : /*#__PURE__*/external_React_default().createElement("span", {
  13918    className: "topic-list-empty-state",
  13919    "data-l10n-id": "newtab-section-mangage-topics-followed-topics-empty-state"
  13920  }), /*#__PURE__*/external_React_default().createElement("h3", {
  13921    "data-l10n-id": "newtab-section-mangage-topics-blocked-topics"
  13922  }), blockedSectionsData.length ? /*#__PURE__*/external_React_default().createElement("ul", {
  13923    className: "topic-list"
  13924  }, blockedSectionsList) : /*#__PURE__*/external_React_default().createElement("span", {
  13925    className: "topic-list-empty-state",
  13926    "data-l10n-id": "newtab-section-mangage-topics-blocked-topics-empty-state"
  13927  }))));
  13928 }
  13929 
  13930 ;// CONCATENATED MODULE: ./content-src/components/WallpaperCategories/WallpaperCategories.jsx
  13931 function WallpaperCategories_extends() { return WallpaperCategories_extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, WallpaperCategories_extends.apply(null, arguments); }
  13932 /* This Source Code Form is subject to the terms of the Mozilla Public
  13933 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  13934 * You can obtain one at http://mozilla.org/MPL/2.0/. */
  13935 
  13936 
  13937 
  13938 
  13939 // eslint-disable-next-line no-shadow
  13940 
  13941 const PREF_WALLPAPER_UPLOADED_PREVIOUSLY = "newtabWallpapers.customWallpaper.uploadedPreviously";
  13942 const PREF_WALLPAPER_UPLOAD_MAX_FILE_SIZE = "newtabWallpapers.customWallpaper.fileSize";
  13943 const PREF_WALLPAPER_UPLOAD_MAX_FILE_SIZE_ENABLED = "newtabWallpapers.customWallpaper.fileSize.enabled";
  13944 
  13945 // Returns a function will not be continuously triggered when called. The
  13946 // function will be triggered if called again after `wait` milliseconds.
  13947 function debounce(func, wait) {
  13948  let timer;
  13949  return (...args) => {
  13950    if (timer) {
  13951      return;
  13952    }
  13953    let wakeUp = () => {
  13954      timer = null;
  13955    };
  13956    timer = setTimeout(wakeUp, wait);
  13957    func.apply(this, args);
  13958  };
  13959 }
  13960 class _WallpaperCategories extends (external_React_default()).PureComponent {
  13961  constructor(props) {
  13962    super(props);
  13963    this.handleColorInput = this.handleColorInput.bind(this);
  13964    this.debouncedHandleChange = debounce(this.handleChange.bind(this), 999);
  13965    this.handleChange = this.handleChange.bind(this);
  13966    this.handleReset = this.handleReset.bind(this);
  13967    this.handleCategory = this.handleCategory.bind(this);
  13968    this.focusCategory = this.focusCategory.bind(this);
  13969    this.handleUpload = this.handleUpload.bind(this);
  13970    this.handleBack = this.handleBack.bind(this);
  13971    this.handleWallpaperListEntered = this.handleWallpaperListEntered.bind(this);
  13972    this.getRGBColors = this.getRGBColors.bind(this);
  13973    this.prefersHighContrastQuery = null;
  13974    this.prefersDarkQuery = null;
  13975    this.categoryRef = []; // store references for wallpaper category list
  13976    this.wallpaperRef = []; // store reference for wallpaper selection list
  13977    this.arrowButtonRef = /*#__PURE__*/external_React_default().createRef(); // Used to focus arrow button when category opens
  13978    this.customColorPickerRef = /*#__PURE__*/external_React_default().createRef(); // Used to determine contrast icon color for custom color picker
  13979    this.customColorInput = /*#__PURE__*/external_React_default().createRef(); // Used to determine contrast icon color for custom color picker
  13980    this.state = {
  13981      activeCategory: null,
  13982      activeCategoryFluentID: null,
  13983      showColorPicker: false,
  13984      inputType: "radio",
  13985      activeId: null,
  13986      customWallpaperErrorType: null,
  13987      focusedCategoryIndex: 0
  13988    };
  13989  }
  13990  componentDidMount() {
  13991    this.prefersDarkQuery = globalThis.matchMedia("(prefers-color-scheme: dark)");
  13992  }
  13993  componentDidUpdate(prevProps) {
  13994    // Walllpaper category subpanel should close when parent menu is closed
  13995    if (this.props.exitEventFired && this.props.exitEventFired !== prevProps.exitEventFired) {
  13996      this.handleBack();
  13997    }
  13998  }
  13999  handleColorInput(event) {
  14000    let {
  14001      id
  14002    } = event.target;
  14003    // Set ID to include hex value of custom color
  14004    id = `solid-color-picker-${event.target.value}`;
  14005    const rgbColors = this.getRGBColors(event.target.value);
  14006 
  14007    // Set background color to custom color
  14008    event.target.style.backgroundColor = `rgb(${rgbColors.toString()})`;
  14009    if (this.customColorPickerRef.current) {
  14010      const colorInputBackground = this.customColorPickerRef.current.children[0].style.backgroundColor;
  14011      this.customColorPickerRef.current.style.backgroundColor = colorInputBackground;
  14012    }
  14013 
  14014    // Set icon color based on the selected color
  14015    const isColorDark = this.isWallpaperColorDark(rgbColors);
  14016    if (this.customColorPickerRef.current) {
  14017      if (isColorDark) {
  14018        this.customColorPickerRef.current.classList.add("is-dark");
  14019      } else {
  14020        this.customColorPickerRef.current.classList.remove("is-dark");
  14021      }
  14022 
  14023      // Remove any possible initial classes
  14024      this.customColorPickerRef.current.classList.remove("custom-color-set", "custom-color-dark", "default-color-set");
  14025    }
  14026 
  14027    // Setting this now so when we remove v1 we don't have to migrate v1 values.
  14028    this.props.setPref("newtabWallpapers.wallpaper", id);
  14029  }
  14030 
  14031  // Note: There's a separate event (debouncedHandleChange) that fires the handleChange
  14032  // event but is delayed so that it doesn't fire multiple events when a user
  14033  // is selecting a custom color background
  14034  handleChange(event) {
  14035    let {
  14036      id
  14037    } = event.target;
  14038 
  14039    // Set ID to include hex value of custom color
  14040    if (id === "solid-color-picker") {
  14041      id = `solid-color-picker-${event.target.value}`;
  14042    }
  14043    this.props.setPref("newtabWallpapers.wallpaper", id);
  14044    const uploadedPreviously = this.props.Prefs.values[PREF_WALLPAPER_UPLOADED_PREVIOUSLY];
  14045    this.handleUserEvent(actionTypes.WALLPAPER_CLICK, {
  14046      selected_wallpaper: id,
  14047      had_previous_wallpaper: !!this.props.activeWallpaper,
  14048      had_uploaded_previously: !!uploadedPreviously
  14049    });
  14050  }
  14051  focusCategory(focusIndex) {
  14052    if (!this.categoryRef) {
  14053      return;
  14054    }
  14055    const el = this.categoryRef[focusIndex];
  14056    if (el) {
  14057      el.focus();
  14058    }
  14059  }
  14060 
  14061  // function implementing arrow navigation for wallpaper category selection
  14062  handleCategoryKeyDown(event, category) {
  14063    const getIndex = this.categoryRef.findIndex(cat => cat.id === category);
  14064    if (getIndex === -1) {
  14065      return; // prevents errors if wallpaper index isn't found when navigating with arrow keys
  14066    }
  14067    const isRTL = document.dir === "rtl"; // returns true if page language is right-to-left
  14068    let eventKey = event.key;
  14069    if (eventKey === "ArrowRight" || eventKey === "ArrowLeft") {
  14070      if (isRTL) {
  14071        eventKey = eventKey === "ArrowRight" ? "ArrowLeft" : "ArrowRight";
  14072      }
  14073    }
  14074    let nextIndex = getIndex;
  14075    if (eventKey === "ArrowRight") {
  14076      nextIndex = getIndex + 1 < this.categoryRef.length ? getIndex + 1 : getIndex;
  14077    } else if (eventKey === "ArrowLeft") {
  14078      nextIndex = getIndex - 1 >= 0 ? getIndex - 1 : getIndex;
  14079    }
  14080    this.setState({
  14081      focusedCategoryIndex: nextIndex
  14082    }, () => this.focusCategory(nextIndex));
  14083  }
  14084 
  14085  // function implementing arrow navigation for wallpaper selection
  14086  handleWallpaperKeyDown(event, title) {
  14087    if (event.key === "Tab") {
  14088      if (event.shiftKey) {
  14089        event.preventDefault();
  14090        this.arrowButtonRef.current?.focus();
  14091      } else {
  14092        event.preventDefault(); // prevent tabbing within wallpaper selection. We should only be using the Tab key to tab between groups
  14093      }
  14094      return;
  14095    }
  14096    const isRTL = document.dir === "rtl"; // returns true if page language is right-to-left
  14097    let eventKey = event.key;
  14098    if (eventKey === "ArrowRight" || eventKey === "ArrowLeft") {
  14099      if (isRTL) {
  14100        eventKey = eventKey === "ArrowRight" ? "ArrowLeft" : "ArrowRight";
  14101      }
  14102    }
  14103    const getIndex = this.wallpaperRef.findIndex(wallpaper => wallpaper.id === title);
  14104    if (getIndex === -1) {
  14105      return; // prevents errors if wallpaper index isn't found when navigating with arrow keys
  14106    }
  14107 
  14108    // the set layout of columns per row for the wallpaper selection
  14109    const columnCount = 3;
  14110    let nextIndex = getIndex;
  14111    if (eventKey === "ArrowRight") {
  14112      nextIndex = getIndex + 1 < this.wallpaperRef.length ? getIndex + 1 : getIndex;
  14113    } else if (eventKey === "ArrowLeft") {
  14114      nextIndex = getIndex - 1 >= 0 ? getIndex - 1 : getIndex;
  14115    } else if (eventKey === "ArrowDown") {
  14116      nextIndex = getIndex + columnCount < this.wallpaperRef.length ? getIndex + columnCount : getIndex;
  14117    } else if (eventKey === "ArrowUp") {
  14118      nextIndex = getIndex - columnCount >= 0 ? getIndex - columnCount : getIndex;
  14119    }
  14120    this.wallpaperRef[nextIndex].tabIndex = 0;
  14121    this.wallpaperRef[getIndex].tabIndex = -1;
  14122    this.wallpaperRef[nextIndex].focus();
  14123    this.wallpaperRef[nextIndex].click();
  14124  }
  14125  handleReset() {
  14126    const uploadedPreviously = this.props.Prefs.values[PREF_WALLPAPER_UPLOADED_PREVIOUSLY];
  14127    const selectedWallpaper = this.props.Prefs.values["newtabWallpapers.wallpaper"];
  14128 
  14129    // If a custom wallpaper is set, remove it
  14130    if (selectedWallpaper === "custom") {
  14131      this.props.dispatch(actionCreators.OnlyToMain({
  14132        type: actionTypes.WALLPAPER_REMOVE_UPLOAD
  14133      }));
  14134    }
  14135 
  14136    // Reset active wallpaper
  14137    this.props.setPref("newtabWallpapers.wallpaper", "");
  14138 
  14139    // Fire WALLPAPER_CLICK telemetry event
  14140    this.handleUserEvent(actionTypes.WALLPAPER_CLICK, {
  14141      selected_wallpaper: "none",
  14142      had_previous_wallpaper: !!this.props.activeWallpaper,
  14143      had_uploaded_previously: !!uploadedPreviously
  14144    });
  14145  }
  14146  handleCategory = event => {
  14147    this.setState({
  14148      activeCategory: event.target.id
  14149    });
  14150    this.handleUserEvent(actionTypes.WALLPAPER_CATEGORY_CLICK, event.target.id);
  14151 
  14152    // Notify parent menu when subpanel opens
  14153    if (this.props.onSubpanelToggle) {
  14154      this.props.onSubpanelToggle(true);
  14155    }
  14156    let fluent_id;
  14157    switch (event.target.id) {
  14158      case "abstracts":
  14159        fluent_id = "newtab-wallpaper-category-title-abstract";
  14160        break;
  14161      case "celestial":
  14162        fluent_id = "newtab-wallpaper-category-title-celestial";
  14163        break;
  14164      case "photographs":
  14165        fluent_id = "newtab-wallpaper-category-title-photographs";
  14166        break;
  14167      case "solid-colors":
  14168        fluent_id = "newtab-wallpaper-category-title-colors";
  14169        break;
  14170      case "firefox":
  14171        fluent_id = "newtab-wallpaper-category-title-firefox";
  14172        break;
  14173    }
  14174    this.setState({
  14175      activeCategoryFluentID: fluent_id
  14176    });
  14177  };
  14178 
  14179  // Custom wallpaper image upload
  14180  async handleUpload() {
  14181    const wallpaperUploadMaxFileSizeEnabled = this.props.Prefs.values[PREF_WALLPAPER_UPLOAD_MAX_FILE_SIZE_ENABLED];
  14182    const wallpaperUploadMaxFileSize = this.props.Prefs.values[PREF_WALLPAPER_UPLOAD_MAX_FILE_SIZE];
  14183    const uploadedPreviously = this.props.Prefs.values[PREF_WALLPAPER_UPLOADED_PREVIOUSLY];
  14184 
  14185    // Create a file input since category buttons are radio inputs
  14186    const fileInput = document.createElement("input");
  14187    fileInput.type = "file";
  14188    fileInput.accept = "image/*"; // only allow image files
  14189 
  14190    // Catch cancel events
  14191    fileInput.oncancel = async () => {
  14192      this.setState({
  14193        customWallpaperErrorType: null
  14194      });
  14195    };
  14196 
  14197    // Reset error state when user begins file selection
  14198    this.setState({
  14199      customWallpaperErrorType: null
  14200    });
  14201 
  14202    // Fire when user selects a file
  14203    fileInput.onchange = async event => {
  14204      const [file] = event.target.files;
  14205      if (file) {
  14206        // Validate file type: Only accept files with a valid image MIME type
  14207        const isValidImage = file.type && file.type.startsWith("image/");
  14208        if (!isValidImage) {
  14209          console.error("Invalid file type");
  14210          this.setState({
  14211            customWallpaperErrorType: "fileType"
  14212          });
  14213          return;
  14214        }
  14215 
  14216        // Limit image uploaded to a maximum file size if enabled
  14217        // Note: The max file size pref (customWallpaper.fileSize) is converted to megabytes (MB)
  14218        // Example: if pref value is 5, max file size is 5 MB
  14219        const maxSize = wallpaperUploadMaxFileSize * 1024 * 1024;
  14220        if (wallpaperUploadMaxFileSizeEnabled && file.size > maxSize) {
  14221          console.error("File size exceeds limit");
  14222          this.setState({
  14223            customWallpaperErrorType: "fileSize"
  14224          });
  14225          return;
  14226        }
  14227        this.props.dispatch(actionCreators.OnlyToMain({
  14228          type: actionTypes.WALLPAPER_UPLOAD,
  14229          data: {
  14230            file
  14231          }
  14232        }));
  14233 
  14234        // Set active wallpaper ID to "custom"
  14235        this.props.setPref("newtabWallpapers.wallpaper", "custom");
  14236 
  14237        // Update the uploadedPreviously pref to TRUE
  14238        // Note: this pref used for telemetry. Do not reset to false.
  14239        this.props.setPref(PREF_WALLPAPER_UPLOADED_PREVIOUSLY, true);
  14240        this.handleUserEvent(actionTypes.WALLPAPER_CLICK, {
  14241          selected_wallpaper: "custom",
  14242          had_previous_wallpaper: !!this.props.activeWallpaper,
  14243          had_uploaded_previously: !!uploadedPreviously
  14244        });
  14245      }
  14246    };
  14247    fileInput.click();
  14248  }
  14249  handleBack() {
  14250    this.setState({
  14251      activeCategory: null
  14252    }, () => {
  14253      // Notify parent menu when subpanel closes
  14254      if (this.props.onSubpanelToggle) {
  14255        this.props.onSubpanelToggle(false);
  14256      }
  14257 
  14258      // Wait for the category grid to be back in the DOM
  14259      requestAnimationFrame(() => {
  14260        this.focusCategory(this.state.focusedCategoryIndex);
  14261      });
  14262    });
  14263  }
  14264  handleWallpaperListEntered() {
  14265    this.arrowButtonRef.current?.focus();
  14266  }
  14267 
  14268  // Record user interaction when changing wallpaper and reseting wallpaper to default
  14269  handleUserEvent(type, data) {
  14270    this.props.dispatch(actionCreators.OnlyToMain({
  14271      type,
  14272      data
  14273    }));
  14274  }
  14275  setActiveId = id => {
  14276    this.setState({
  14277      activeId: id
  14278    }); // Set the active ID
  14279  };
  14280  getRGBColors(input) {
  14281    if (input.length !== 7) {
  14282      return [];
  14283    }
  14284    const r = parseInt(input.substr(1, 2), 16);
  14285    const g = parseInt(input.substr(3, 2), 16);
  14286    const b = parseInt(input.substr(5, 2), 16);
  14287    return [r, g, b];
  14288  }
  14289  isWallpaperColorDark([r, g, b]) {
  14290    return 0.2125 * r + 0.7154 * g + 0.0721 * b <= 110;
  14291  }
  14292  sortWallpapersByOrder(wallpapers) {
  14293    return wallpapers.sort((a, b) => {
  14294      const aOrder = a.order || 0;
  14295      const bOrder = b.order || 0;
  14296      if (aOrder === 0 && bOrder === 0) {
  14297        return 0;
  14298      }
  14299      if (aOrder === 0) {
  14300        return 1;
  14301      }
  14302      if (bOrder === 0) {
  14303        return -1;
  14304      }
  14305      return aOrder - bOrder;
  14306    });
  14307  }
  14308  render() {
  14309    const prefs = this.props.Prefs.values;
  14310    const {
  14311      wallpaperList,
  14312      categories
  14313    } = this.props.Wallpapers;
  14314    const {
  14315      activeWallpaper
  14316    } = this.props;
  14317    const {
  14318      activeCategory,
  14319      showColorPicker
  14320    } = this.state;
  14321    const {
  14322      activeCategoryFluentID
  14323    } = this.state;
  14324    let filteredWallpapers = wallpaperList.filter(wallpaper => wallpaper.category === activeCategory);
  14325    const wallpaperUploadMaxFileSize = this.props.Prefs.values[PREF_WALLPAPER_UPLOAD_MAX_FILE_SIZE];
  14326    function reduceColorsToFitCustomColorInput(arr) {
  14327      // Reduce the amount of custom colors to make space for the custom color picker
  14328      while (arr.length % 3 !== 2) {
  14329        arr.pop();
  14330      }
  14331      return arr;
  14332    }
  14333    let wallpaperCustomSolidColorHex = null;
  14334    const selectedWallpaper = prefs["newtabWallpapers.wallpaper"];
  14335 
  14336    // User has previous selected a custom color
  14337    if (selectedWallpaper.includes("solid-color-picker")) {
  14338      this.setState({
  14339        showColorPicker: true
  14340      });
  14341      const regex = /#([a-fA-F0-9]{6})/;
  14342      [wallpaperCustomSolidColorHex] = selectedWallpaper.match(regex);
  14343    }
  14344 
  14345    // Enable custom color select if pref'ed on
  14346    this.setState({
  14347      showColorPicker: prefs["newtabWallpapers.customColor.enabled"]
  14348    });
  14349 
  14350    // Remove last item of solid colors to make space for custom color picker
  14351    if (prefs["newtabWallpapers.customColor.enabled"] && activeCategory === "solid-colors") {
  14352      filteredWallpapers = reduceColorsToFitCustomColorInput(filteredWallpapers);
  14353    }
  14354 
  14355    // Bug 1953012 - If nothing selected, default to color of customize panel
  14356    // --color-blue-70 : #054096
  14357    // --color-blue-05 : #deeafc
  14358    const starterColorHex = this.prefersDarkQuery?.matches ? "#054096" : "#deeafc";
  14359 
  14360    // Set initial state of the color picker (depending if the user has already set a custom color)
  14361    let initStateClassname = wallpaperCustomSolidColorHex ? "custom-color-set" : "default-color-set";
  14362 
  14363    // If a custom color picker is set, make sure the icon has the correct contrast
  14364    if (wallpaperCustomSolidColorHex) {
  14365      const rgbColors = this.getRGBColors(wallpaperCustomSolidColorHex);
  14366      const isColorDark = this.isWallpaperColorDark(rgbColors);
  14367      if (isColorDark) {
  14368        initStateClassname += " custom-color-dark";
  14369      }
  14370    }
  14371    let colorPickerInput = showColorPicker && activeCategory === "solid-colors" ? /*#__PURE__*/external_React_default().createElement("div", {
  14372      className: `theme-custom-color-picker ${initStateClassname}`,
  14373      ref: this.customColorPickerRef
  14374    }, /*#__PURE__*/external_React_default().createElement("input", {
  14375      onInput: this.handleColorInput,
  14376      onChange: this.debouncedHandleChange,
  14377      onClick: () => this.setActiveId("solid-color-picker") //
  14378      ,
  14379      type: "color",
  14380      name: `wallpaper-solid-color-picker`,
  14381      id: "solid-color-picker"
  14382      // aria-checked is not applicable for input[type="color"] elements
  14383      ,
  14384      "aria-current": this.state.activeId === "solid-color-picker",
  14385      value: wallpaperCustomSolidColorHex || starterColorHex,
  14386      className: `wallpaper-input
  14387              ${this.state.activeId === "solid-color-picker" ? "active" : ""}`,
  14388      ref: this.customColorInput
  14389    }), /*#__PURE__*/external_React_default().createElement("label", {
  14390      htmlFor: "solid-color-picker",
  14391      "data-l10n-id": "newtab-wallpaper-custom-color"
  14392    })) : "";
  14393    return /*#__PURE__*/external_React_default().createElement("div", null, /*#__PURE__*/external_React_default().createElement("div", {
  14394      className: "category-header"
  14395    }, /*#__PURE__*/external_React_default().createElement("h2", {
  14396      "data-l10n-id": "newtab-wallpaper-title"
  14397    }), /*#__PURE__*/external_React_default().createElement("button", {
  14398      className: "wallpapers-reset",
  14399      onClick: this.handleReset,
  14400      "data-l10n-id": "newtab-wallpaper-reset"
  14401    })), /*#__PURE__*/external_React_default().createElement("div", {
  14402      role: "grid",
  14403      "aria-label": "Wallpaper category selection. Use arrow keys to navigate."
  14404    }, /*#__PURE__*/external_React_default().createElement("fieldset", {
  14405      className: "category-list"
  14406    }, categories.map((category, index) => {
  14407      const filteredList = wallpaperList.filter(wallpaper => wallpaper.category === category);
  14408      const sortedList = this.sortWallpapersByOrder(filteredList);
  14409      const activeWallpaperObj = activeWallpaper && sortedList.find(wp => wp.title === activeWallpaper);
  14410      // Detect custom solid color
  14411      const isCustomSolidColor = category === "solid-colors" && activeWallpaper.startsWith("solid-color-picker");
  14412      const thumbnail = activeWallpaperObj || sortedList[0];
  14413      let fluent_id;
  14414      switch (category) {
  14415        case "abstracts":
  14416          fluent_id = "newtab-wallpaper-category-title-abstract";
  14417          break;
  14418        case "celestial":
  14419          fluent_id = "newtab-wallpaper-category-title-celestial";
  14420          break;
  14421        case "custom-wallpaper":
  14422          fluent_id = "newtab-wallpaper-upload-image";
  14423          break;
  14424        case "photographs":
  14425          fluent_id = "newtab-wallpaper-category-title-photographs";
  14426          break;
  14427        case "solid-colors":
  14428          fluent_id = "newtab-wallpaper-category-title-colors";
  14429          break;
  14430        case "firefox":
  14431          fluent_id = "newtab-wallpaper-category-title-firefox";
  14432          break;
  14433      }
  14434      let style = {};
  14435      if (thumbnail?.wallpaperUrl) {
  14436        style.backgroundImage = `url(${thumbnail.wallpaperUrl})`;
  14437        style.backgroundPosition = thumbnail.background_position || "center";
  14438      } else {
  14439        style.backgroundColor = thumbnail?.solid_color || "";
  14440      }
  14441      // If custom solid color is active, override the thumbnail to the chosen hex
  14442      if (isCustomSolidColor) {
  14443        const hex = activeWallpaper.split("solid-color-picker-")[1] || "";
  14444        style.backgroundColor = hex;
  14445      }
  14446      const isCategorySelected = activeWallpaperObj || isCustomSolidColor;
  14447      return /*#__PURE__*/external_React_default().createElement("div", {
  14448        key: category
  14449      }, /*#__PURE__*/external_React_default().createElement("button", WallpaperCategories_extends({
  14450        ref: el => {
  14451          if (el) {
  14452            this.categoryRef[index] = el;
  14453          }
  14454        },
  14455        id: category,
  14456        style: style,
  14457        onKeyDown: e => this.handleCategoryKeyDown(e, category)
  14458        // Add overrides for custom wallpaper upload UI
  14459        ,
  14460        onClick: event => {
  14461          this.setState({
  14462            focusedCategoryIndex: index
  14463          });
  14464          if (category !== "custom-wallpaper") {
  14465            this.handleCategory(event);
  14466          } else {
  14467            this.handleUpload();
  14468          }
  14469        },
  14470        className: `wallpaper-input
  14471                      ${category === "custom-wallpaper" ? "theme-custom-wallpaper" : ""}
  14472                      ${isCategorySelected ? "selected" : ""}`,
  14473        tabIndex: this.state.focusedCategoryIndex === index ? 0 : -1
  14474      }, category === "custom-wallpaper" ? {
  14475        "aria-errormessage": "customWallpaperError"
  14476      } : {})), /*#__PURE__*/external_React_default().createElement("label", {
  14477        htmlFor: category,
  14478        "data-l10n-id": fluent_id
  14479      }, fluent_id));
  14480    })), this.state.customWallpaperErrorType && /*#__PURE__*/external_React_default().createElement("div", {
  14481      className: "custom-wallpaper-error",
  14482      id: "customWallpaperError"
  14483    }, /*#__PURE__*/external_React_default().createElement("span", {
  14484      className: "icon icon-info"
  14485    }), (() => {
  14486      switch (this.state.customWallpaperErrorType) {
  14487        case "fileSize":
  14488          return /*#__PURE__*/external_React_default().createElement("span", {
  14489            "data-l10n-id": "newtab-wallpaper-error-max-file-size",
  14490            "data-l10n-args": `{"file_size": ${wallpaperUploadMaxFileSize}}`
  14491          });
  14492        case "fileType":
  14493          return /*#__PURE__*/external_React_default().createElement("span", {
  14494            "data-l10n-id": "newtab-wallpaper-error-upload-file-type"
  14495          });
  14496        default:
  14497          return null;
  14498      }
  14499    })())), /*#__PURE__*/external_React_default().createElement(external_ReactTransitionGroup_namespaceObject.CSSTransition, {
  14500      in: !!activeCategory,
  14501      timeout: 300,
  14502      classNames: "wallpaper-list",
  14503      unmountOnExit: true,
  14504      onEntered: this.handleWallpaperListEntered
  14505    }, /*#__PURE__*/external_React_default().createElement("section", {
  14506      className: "category wallpaper-list ignore-color-mode"
  14507    }, /*#__PURE__*/external_React_default().createElement("button", {
  14508      ref: this.arrowButtonRef,
  14509      className: "arrow-button",
  14510      "data-l10n-id": activeCategoryFluentID,
  14511      onClick: this.handleBack
  14512    }), /*#__PURE__*/external_React_default().createElement("div", {
  14513      role: "grid",
  14514      "aria-label": "Wallpaper selection. Use arrow keys to navigate."
  14515    }, /*#__PURE__*/external_React_default().createElement("fieldset", null, this.sortWallpapersByOrder(filteredWallpapers).map(({
  14516      background_position,
  14517      fluent_id,
  14518      solid_color,
  14519      theme,
  14520      title,
  14521      wallpaperUrl
  14522    }, index) => {
  14523      let style = {};
  14524      if (wallpaperUrl) {
  14525        style.backgroundImage = `url(${wallpaperUrl})`;
  14526        style.backgroundPosition = background_position || "center";
  14527      } else {
  14528        style.backgroundColor = solid_color || "";
  14529      }
  14530      return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement("input", {
  14531        ref: el => {
  14532          if (el) {
  14533            this.wallpaperRef[index] = el;
  14534          }
  14535        },
  14536        onChange: this.handleChange,
  14537        onKeyDown: e => this.handleWallpaperKeyDown(e, title),
  14538        style: style,
  14539        type: "radio",
  14540        name: `wallpaper-${title}`,
  14541        id: title,
  14542        value: title,
  14543        checked: title === activeWallpaper,
  14544        "aria-checked": title === activeWallpaper,
  14545        className: `wallpaper-input theme-${theme} ${this.state.activeId === title ? "active" : ""}`,
  14546        onClick: () => this.setActiveId(title) //
  14547        ,
  14548        tabIndex: index === 0 ? 0 : -1 //the first wallpaper in the array will have a tabindex of 0 so we can tab into it. The rest will have a tabindex of -1
  14549      }), /*#__PURE__*/external_React_default().createElement("label", {
  14550        htmlFor: title,
  14551        className: "sr-only",
  14552        "data-l10n-id": fluent_id
  14553      }, fluent_id));
  14554    }), colorPickerInput)))));
  14555  }
  14556 }
  14557 const WallpaperCategories = (0,external_ReactRedux_namespaceObject.connect)(state => {
  14558  return {
  14559    Wallpapers: state.Wallpapers,
  14560    Prefs: state.Prefs
  14561  };
  14562 })(_WallpaperCategories);
  14563 ;// CONCATENATED MODULE: ./content-src/components/CustomizeMenu/ContentSection/ContentSection.jsx
  14564 function ContentSection_extends() { return ContentSection_extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, ContentSection_extends.apply(null, arguments); }
  14565 /* This Source Code Form is subject to the terms of the Mozilla Public
  14566 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  14567 * You can obtain one at http://mozilla.org/MPL/2.0/. */
  14568 
  14569 
  14570 
  14571 
  14572 
  14573 class ContentSection extends (external_React_default()).PureComponent {
  14574  constructor(props) {
  14575    super(props);
  14576    this.onPreferenceSelect = this.onPreferenceSelect.bind(this);
  14577 
  14578    // Refs are necessary for dynamically measuring drawer heights for slide animations
  14579    this.topSitesDrawerRef = /*#__PURE__*/external_React_default().createRef();
  14580    this.pocketDrawerRef = /*#__PURE__*/external_React_default().createRef();
  14581  }
  14582  inputUserEvent(eventSource, eventValue) {
  14583    this.props.dispatch(actionCreators.UserEvent({
  14584      event: "PREF_CHANGED",
  14585      source: eventSource,
  14586      value: {
  14587        status: eventValue,
  14588        menu_source: "CUSTOMIZE_MENU"
  14589      }
  14590    }));
  14591  }
  14592  onPreferenceSelect(e) {
  14593    // eventSource: WEATHER | TOP_SITES | TOP_STORIES | WIDGET_LISTS | WIDGET_TIMER
  14594    const {
  14595      preference,
  14596      eventSource
  14597    } = e.target.dataset;
  14598    let value;
  14599    if (e.target.nodeName === "SELECT") {
  14600      value = parseInt(e.target.value, 10);
  14601    } else if (e.target.nodeName === "INPUT") {
  14602      value = e.target.checked;
  14603      if (eventSource) {
  14604        this.inputUserEvent(eventSource, value);
  14605      }
  14606    } else if (e.target.nodeName === "MOZ-TOGGLE") {
  14607      value = e.target.pressed;
  14608      if (eventSource) {
  14609        this.inputUserEvent(eventSource, value);
  14610      }
  14611    }
  14612    this.props.setPref(preference, value);
  14613  }
  14614  componentDidMount() {
  14615    this.setDrawerMargins();
  14616  }
  14617  componentDidUpdate() {
  14618    this.setDrawerMargins();
  14619  }
  14620  setDrawerMargins() {
  14621    this.setDrawerMargin(`TOP_SITES`, this.props.enabledSections.topSitesEnabled);
  14622    this.setDrawerMargin(`TOP_STORIES`, this.props.enabledSections.pocketEnabled);
  14623  }
  14624  setDrawerMargin(drawerID, isOpen) {
  14625    let drawerRef;
  14626    if (drawerID === `TOP_SITES`) {
  14627      drawerRef = this.topSitesDrawerRef.current;
  14628    } else if (drawerID === `TOP_STORIES`) {
  14629      drawerRef = this.pocketDrawerRef.current;
  14630    } else {
  14631      return;
  14632    }
  14633    if (drawerRef) {
  14634      // Use measured height if valid, otherwise use a large fallback
  14635      // since overflow:hidden on the parent safely hides the drawer
  14636      let drawerHeight = parseFloat(window.getComputedStyle(drawerRef)?.height) || 100;
  14637      if (isOpen) {
  14638        drawerRef.style.marginTop = "var(--space-small)";
  14639      } else {
  14640        drawerRef.style.marginTop = `-${drawerHeight + 3}px`;
  14641      }
  14642    }
  14643  }
  14644  render() {
  14645    const {
  14646      enabledSections,
  14647      enabledWidgets,
  14648      pocketRegion,
  14649      mayHaveInferredPersonalization,
  14650      mayHaveWeather,
  14651      mayHaveWidgets,
  14652      mayHaveTimerWidget,
  14653      mayHaveListsWidget,
  14654      openPreferences,
  14655      wallpapersEnabled,
  14656      activeWallpaper,
  14657      setPref,
  14658      mayHaveTopicSections,
  14659      exitEventFired,
  14660      onSubpanelToggle,
  14661      toggleSectionsMgmtPanel,
  14662      showSectionsMgmtPanel
  14663    } = this.props;
  14664    const {
  14665      topSitesEnabled,
  14666      pocketEnabled,
  14667      weatherEnabled,
  14668      showInferredPersonalizationEnabled,
  14669      topSitesRowsCount
  14670    } = enabledSections;
  14671    const {
  14672      timerEnabled,
  14673      listsEnabled
  14674    } = enabledWidgets;
  14675    return /*#__PURE__*/external_React_default().createElement("div", {
  14676      className: "home-section"
  14677    }, wallpapersEnabled && /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement("div", {
  14678      className: "wallpapers-section"
  14679    }, /*#__PURE__*/external_React_default().createElement(WallpaperCategories, {
  14680      setPref: setPref,
  14681      activeWallpaper: activeWallpaper,
  14682      exitEventFired: exitEventFired,
  14683      onSubpanelToggle: onSubpanelToggle
  14684    })), !mayHaveWidgets && /*#__PURE__*/external_React_default().createElement("span", {
  14685      className: "divider",
  14686      role: "separator"
  14687    })), mayHaveWidgets && /*#__PURE__*/external_React_default().createElement("div", {
  14688      className: "widgets-section"
  14689    }, /*#__PURE__*/external_React_default().createElement("div", {
  14690      className: "category-header"
  14691    }, /*#__PURE__*/external_React_default().createElement("h2", {
  14692      "data-l10n-id": "newtab-custom-widget-section-title"
  14693    })), /*#__PURE__*/external_React_default().createElement("div", {
  14694      className: "settings-widgets"
  14695    }, mayHaveWeather && /*#__PURE__*/external_React_default().createElement("div", {
  14696      id: "weather-section",
  14697      className: "section"
  14698    }, /*#__PURE__*/external_React_default().createElement("moz-toggle", {
  14699      id: "weather-toggle",
  14700      pressed: weatherEnabled || null,
  14701      onToggle: this.onPreferenceSelect,
  14702      "data-preference": "showWeather",
  14703      "data-eventSource": "WEATHER",
  14704      "data-l10n-id": "newtab-custom-widget-weather-toggle"
  14705    })), mayHaveListsWidget && /*#__PURE__*/external_React_default().createElement("div", {
  14706      id: "lists-widget-section",
  14707      className: "section"
  14708    }, /*#__PURE__*/external_React_default().createElement("moz-toggle", {
  14709      id: "lists-toggle",
  14710      pressed: listsEnabled || null,
  14711      onToggle: this.onPreferenceSelect,
  14712      "data-preference": "widgets.lists.enabled",
  14713      "data-eventSource": "WIDGET_LISTS",
  14714      "data-l10n-id": "newtab-custom-widget-lists-toggle"
  14715    })), mayHaveTimerWidget && /*#__PURE__*/external_React_default().createElement("div", {
  14716      id: "timer-widget-section",
  14717      className: "section"
  14718    }, /*#__PURE__*/external_React_default().createElement("moz-toggle", {
  14719      id: "timer-toggle",
  14720      pressed: timerEnabled || null,
  14721      onToggle: this.onPreferenceSelect,
  14722      "data-preference": "widgets.focusTimer.enabled",
  14723      "data-eventSource": "WIDGET_TIMER",
  14724      "data-l10n-id": "newtab-custom-widget-timer-toggle"
  14725    })), /*#__PURE__*/external_React_default().createElement("span", {
  14726      className: "divider",
  14727      role: "separator"
  14728    }))), /*#__PURE__*/external_React_default().createElement("div", {
  14729      className: "settings-toggles"
  14730    }, !mayHaveWidgets && mayHaveWeather && /*#__PURE__*/external_React_default().createElement("div", {
  14731      id: "weather-section",
  14732      className: "section"
  14733    }, /*#__PURE__*/external_React_default().createElement("moz-toggle", {
  14734      id: "weather-toggle",
  14735      pressed: weatherEnabled || null,
  14736      onToggle: this.onPreferenceSelect,
  14737      "data-preference": "showWeather",
  14738      "data-eventSource": "WEATHER",
  14739      "data-l10n-id": "newtab-custom-weather-toggle"
  14740    })), /*#__PURE__*/external_React_default().createElement("div", {
  14741      id: "shortcuts-section",
  14742      className: "section"
  14743    }, /*#__PURE__*/external_React_default().createElement("moz-toggle", {
  14744      id: "shortcuts-toggle",
  14745      pressed: topSitesEnabled || null,
  14746      onToggle: this.onPreferenceSelect,
  14747      "data-preference": "feeds.topsites",
  14748      "data-eventSource": "TOP_SITES",
  14749      "data-l10n-id": "newtab-custom-shortcuts-toggle"
  14750    }, /*#__PURE__*/external_React_default().createElement("div", {
  14751      slot: "nested"
  14752    }, /*#__PURE__*/external_React_default().createElement("div", {
  14753      className: "more-info-top-wrapper"
  14754    }, /*#__PURE__*/external_React_default().createElement("div", {
  14755      className: "more-information",
  14756      ref: this.topSitesDrawerRef
  14757    }, /*#__PURE__*/external_React_default().createElement("select", {
  14758      id: "row-selector",
  14759      className: "selector",
  14760      name: "row-count",
  14761      "data-preference": "topSitesRows",
  14762      value: topSitesRowsCount,
  14763      onChange: this.onPreferenceSelect,
  14764      disabled: !topSitesEnabled,
  14765      "aria-labelledby": "custom-shortcuts-title"
  14766    }, /*#__PURE__*/external_React_default().createElement("option", {
  14767      value: "1",
  14768      "data-l10n-id": "newtab-custom-row-selector",
  14769      "data-l10n-args": "{\"num\": 1}"
  14770    }), /*#__PURE__*/external_React_default().createElement("option", {
  14771      value: "2",
  14772      "data-l10n-id": "newtab-custom-row-selector",
  14773      "data-l10n-args": "{\"num\": 2}"
  14774    }), /*#__PURE__*/external_React_default().createElement("option", {
  14775      value: "3",
  14776      "data-l10n-id": "newtab-custom-row-selector",
  14777      "data-l10n-args": "{\"num\": 3}"
  14778    }), /*#__PURE__*/external_React_default().createElement("option", {
  14779      value: "4",
  14780      "data-l10n-id": "newtab-custom-row-selector",
  14781      "data-l10n-args": "{\"num\": 4}"
  14782    }))))))), pocketRegion && /*#__PURE__*/external_React_default().createElement("div", {
  14783      id: "pocket-section",
  14784      className: "section"
  14785    }, /*#__PURE__*/external_React_default().createElement("moz-toggle", ContentSection_extends({
  14786      id: "pocket-toggle",
  14787      pressed: pocketEnabled || null,
  14788      onToggle: this.onPreferenceSelect,
  14789      "aria-describedby": "custom-pocket-subtitle",
  14790      "data-preference": "feeds.section.topstories",
  14791      "data-eventSource": "TOP_STORIES"
  14792    }, mayHaveInferredPersonalization ? {
  14793      "data-l10n-id": "newtab-custom-stories-personalized-toggle"
  14794    } : {
  14795      "data-l10n-id": "newtab-custom-stories-toggle"
  14796    }), /*#__PURE__*/external_React_default().createElement("div", {
  14797      slot: "nested"
  14798    }, (mayHaveInferredPersonalization || mayHaveTopicSections) && /*#__PURE__*/external_React_default().createElement("div", {
  14799      className: "more-info-pocket-wrapper"
  14800    }, /*#__PURE__*/external_React_default().createElement("div", {
  14801      className: "more-information",
  14802      ref: this.pocketDrawerRef
  14803    }, mayHaveInferredPersonalization && /*#__PURE__*/external_React_default().createElement("div", {
  14804      className: "check-wrapper",
  14805      role: "presentation"
  14806    }, /*#__PURE__*/external_React_default().createElement("input", {
  14807      id: "inferred-personalization",
  14808      className: "customize-menu-checkbox",
  14809      disabled: !pocketEnabled,
  14810      checked: showInferredPersonalizationEnabled,
  14811      type: "checkbox",
  14812      onChange: this.onPreferenceSelect,
  14813      "data-preference": "discoverystream.sections.personalization.inferred.user.enabled",
  14814      "data-eventSource": "INFERRED_PERSONALIZATION"
  14815    }), /*#__PURE__*/external_React_default().createElement("label", {
  14816      className: "customize-menu-checkbox-label",
  14817      htmlFor: "inferred-personalization",
  14818      "data-l10n-id": "newtab-custom-stories-personalized-checkbox-label"
  14819    })), mayHaveTopicSections && /*#__PURE__*/external_React_default().createElement(SectionsMgmtPanel, {
  14820      exitEventFired: exitEventFired,
  14821      pocketEnabled: pocketEnabled,
  14822      onSubpanelToggle: onSubpanelToggle,
  14823      togglePanel: toggleSectionsMgmtPanel,
  14824      showPanel: showSectionsMgmtPanel
  14825    }))))))), /*#__PURE__*/external_React_default().createElement("span", {
  14826      className: "divider",
  14827      role: "separator"
  14828    }), /*#__PURE__*/external_React_default().createElement("div", null, /*#__PURE__*/external_React_default().createElement("button", {
  14829      id: "settings-link",
  14830      className: "external-link",
  14831      onClick: openPreferences,
  14832      "data-l10n-id": "newtab-custom-settings"
  14833    })));
  14834  }
  14835 }
  14836 ;// CONCATENATED MODULE: ./content-src/components/CustomizeMenu/CustomizeMenu.jsx
  14837 /* This Source Code Form is subject to the terms of the Mozilla Public
  14838 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  14839 * You can obtain one at http://mozilla.org/MPL/2.0/. */
  14840 
  14841 
  14842 
  14843 
  14844 // eslint-disable-next-line no-shadow
  14845 
  14846 class _CustomizeMenu extends (external_React_default()).PureComponent {
  14847  constructor(props) {
  14848    super(props);
  14849    this.onEntered = this.onEntered.bind(this);
  14850    this.onExited = this.onExited.bind(this);
  14851    this.onSubpanelToggle = this.onSubpanelToggle.bind(this);
  14852    this.state = {
  14853      exitEventFired: false,
  14854      subpanelOpen: false
  14855    };
  14856  }
  14857  onSubpanelToggle(isOpen) {
  14858    this.setState({
  14859      subpanelOpen: isOpen
  14860    });
  14861  }
  14862  onEntered() {
  14863    this.setState({
  14864      exitEventFired: false
  14865    });
  14866    if (this.closeButton) {
  14867      this.closeButton.focus();
  14868    }
  14869  }
  14870  onExited() {
  14871    this.setState({
  14872      exitEventFired: true
  14873    });
  14874    if (this.openButton) {
  14875      this.openButton.focus();
  14876    }
  14877  }
  14878  render() {
  14879    return /*#__PURE__*/external_React_default().createElement("span", null, /*#__PURE__*/external_React_default().createElement(external_ReactTransitionGroup_namespaceObject.CSSTransition, {
  14880      timeout: 300,
  14881      classNames: "personalize-animate",
  14882      in: !this.props.showing,
  14883      appear: true
  14884    }, /*#__PURE__*/external_React_default().createElement("button", {
  14885      className: "personalize-button",
  14886      "data-l10n-id": "newtab-customize-panel-icon-button",
  14887      onClick: () => this.props.onOpen(),
  14888      onKeyDown: e => {
  14889        if (e.key === "Enter") {
  14890          this.props.onOpen();
  14891        }
  14892      },
  14893      ref: c => this.openButton = c
  14894    }, /*#__PURE__*/external_React_default().createElement("label", {
  14895      "data-l10n-id": "newtab-customize-panel-icon-button-label"
  14896    }), /*#__PURE__*/external_React_default().createElement("div", null, /*#__PURE__*/external_React_default().createElement("img", {
  14897      role: "presentation",
  14898      src: "chrome://global/skin/icons/edit-outline.svg"
  14899    })))), /*#__PURE__*/external_React_default().createElement(external_ReactTransitionGroup_namespaceObject.CSSTransition, {
  14900      timeout: 250,
  14901      classNames: "customize-animate",
  14902      in: this.props.showing,
  14903      onEntered: this.onEntered,
  14904      onExited: this.onExited,
  14905      appear: true
  14906    }, /*#__PURE__*/external_React_default().createElement("div", {
  14907      className: "customize-menu-animate-wrapper"
  14908    }, /*#__PURE__*/external_React_default().createElement("div", {
  14909      className: `customize-menu ${this.state.subpanelOpen ? "subpanel-open" : ""}`,
  14910      role: "dialog",
  14911      "data-l10n-id": "newtab-settings-dialog-label"
  14912    }, /*#__PURE__*/external_React_default().createElement("div", {
  14913      className: "close-button-wrapper"
  14914    }, /*#__PURE__*/external_React_default().createElement("moz-button", {
  14915      onClick: () => this.props.onClose(),
  14916      id: "close-button",
  14917      type: "icon ghost",
  14918      "data-l10n-id": "newtab-custom-close-menu-button",
  14919      iconsrc: "chrome://global/skin/icons/close.svg",
  14920      ref: c => this.closeButton = c
  14921    })), /*#__PURE__*/external_React_default().createElement(ContentSection, {
  14922      openPreferences: this.props.openPreferences,
  14923      setPref: this.props.setPref,
  14924      enabledSections: this.props.enabledSections,
  14925      enabledWidgets: this.props.enabledWidgets,
  14926      wallpapersEnabled: this.props.wallpapersEnabled,
  14927      activeWallpaper: this.props.activeWallpaper,
  14928      pocketRegion: this.props.pocketRegion,
  14929      mayHaveTopicSections: this.props.mayHaveTopicSections,
  14930      mayHaveInferredPersonalization: this.props.mayHaveInferredPersonalization,
  14931      mayHaveWeather: this.props.mayHaveWeather,
  14932      mayHaveWidgets: this.props.mayHaveWidgets,
  14933      mayHaveTimerWidget: this.props.mayHaveTimerWidget,
  14934      mayHaveListsWidget: this.props.mayHaveListsWidget,
  14935      dispatch: this.props.dispatch,
  14936      exitEventFired: this.state.exitEventFired,
  14937      onSubpanelToggle: this.onSubpanelToggle,
  14938      toggleSectionsMgmtPanel: this.props.toggleSectionsMgmtPanel,
  14939      showSectionsMgmtPanel: this.props.showSectionsMgmtPanel
  14940    })))));
  14941  }
  14942 }
  14943 const CustomizeMenu = (0,external_ReactRedux_namespaceObject.connect)(state => ({
  14944  DiscoveryStream: state.DiscoveryStream
  14945 }))(_CustomizeMenu);
  14946 ;// CONCATENATED MODULE: ./content-src/components/Logo/Logo.jsx
  14947 /* This Source Code Form is subject to the terms of the Mozilla Public
  14948 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  14949 * You can obtain one at http://mozilla.org/MPL/2.0/. */
  14950 
  14951 
  14952 function Logo() {
  14953  return /*#__PURE__*/external_React_default().createElement("h1", {
  14954    className: "logo-and-wordmark-wrapper"
  14955  }, /*#__PURE__*/external_React_default().createElement("div", {
  14956    className: "logo-and-wordmark",
  14957    role: "img",
  14958    "data-l10n-id": "newtab-logo-and-wordmark"
  14959  }, /*#__PURE__*/external_React_default().createElement("div", {
  14960    className: "logo"
  14961  }), /*#__PURE__*/external_React_default().createElement("div", {
  14962    className: "wordmark"
  14963  })));
  14964 }
  14965 
  14966 ;// CONCATENATED MODULE: ./content-src/components/ExternalComponentWrapper/ExternalComponentWrapper.jsx
  14967 /* This Source Code Form is subject to the terms of the Mozilla Public
  14968 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  14969 * You can obtain one at http://mozilla.org/MPL/2.0/. */
  14970 
  14971 
  14972 
  14973 
  14974 /**
  14975 * A React component that dynamically loads and embeds external custom elements
  14976 * into the newtab page.
  14977 *
  14978 * This component serves as a bridge between React's declarative rendering and
  14979 * browser-native custom elements that are registered and managed outside of
  14980 * React's control. It:
  14981 *
  14982 * 1. Looks up the component configuration by type from the ExternalComponents
  14983 *    registry
  14984 * 2. Dynamically imports the component's script module (which registers the
  14985 *    custom element)
  14986 * 3. Creates an instance of the custom element using imperative DOM APIs
  14987 * 4. Appends it to a React-managed container div
  14988 * 5. Cleans up the custom element on unmount
  14989 *
  14990 * This approach is necessary because:
  14991 * - Custom elements have their own lifecycle separate from React
  14992 * - They need to be created imperatively (document.createElement) rather than
  14993 *   declaratively (JSX)
  14994 * - React shouldn't try to diff/reconcile their internal DOM, as they manage
  14995 *   their own shadow DOM
  14996 * - We need manual cleanup to prevent memory leaks when the component unmounts
  14997 *
  14998 * @param {object} props
  14999 * @param {string} props.type - The component type to load (e.g., "SEARCH")
  15000 * @param {string} props.className - CSS class name(s) to apply to the wrapper div
  15001 * @param {Function} props.importModule - Function to import modules (for testing)
  15002 */
  15003 function ExternalComponentWrapper({
  15004  type,
  15005  className,
  15006  // importFunction is declared as an arrow function here purely so that we can
  15007  // override it for testing.
  15008  // eslint-disable-next-line no-unsanitized/method
  15009  importModule = url => import(/* webpackIgnore: true */url)
  15010 }) {
  15011  const containerRef = external_React_default().useRef(null);
  15012  const customElementRef = external_React_default().useRef(null);
  15013  const l10nLinksRef = external_React_default().useRef([]);
  15014  const [error, setError] = external_React_default().useState(null);
  15015  const {
  15016    components
  15017  } = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.ExternalComponents);
  15018  external_React_default().useEffect(() => {
  15019    const container = containerRef.current;
  15020    const loadComponent = async () => {
  15021      try {
  15022        const config = components.find(c => c.type === type);
  15023        if (!config) {
  15024          console.warn(`No external component configuration found for type: ${type}`);
  15025          return;
  15026        }
  15027        await importModule(config.componentURL);
  15028        l10nLinksRef.current = [];
  15029        for (let l10nURL of config.l10nURLs) {
  15030          const l10nEl = document.createElement("link");
  15031          l10nEl.rel = "localization";
  15032          l10nEl.href = l10nURL;
  15033          document.head.appendChild(l10nEl);
  15034          l10nLinksRef.current.push(l10nEl);
  15035        }
  15036        if (containerRef.current && !customElementRef.current) {
  15037          const element = document.createElement(config.tagName);
  15038          if (config.attributes) {
  15039            for (const [key, value] of Object.entries(config.attributes)) {
  15040              element.setAttribute(key, value);
  15041            }
  15042          }
  15043          if (config.cssVariables) {
  15044            for (const [variable, style] of Object.entries(config.cssVariables)) {
  15045              element.style.setProperty(variable, style);
  15046            }
  15047          }
  15048          customElementRef.current = element;
  15049          containerRef.current.appendChild(element);
  15050        }
  15051      } catch (err) {
  15052        console.error(`Failed to load external component for type ${type}:`, err);
  15053        setError(err);
  15054      }
  15055    };
  15056    loadComponent();
  15057    return () => {
  15058      if (customElementRef.current && container) {
  15059        container.removeChild(customElementRef.current);
  15060        customElementRef.current = null;
  15061      }
  15062      for (const link of l10nLinksRef.current) {
  15063        link.remove();
  15064      }
  15065      l10nLinksRef.current = [];
  15066    };
  15067  }, [type, components, importModule]);
  15068  if (error) {
  15069    return null;
  15070  }
  15071  return /*#__PURE__*/external_React_default().createElement("div", {
  15072    ref: containerRef,
  15073    className: className
  15074  });
  15075 }
  15076 
  15077 ;// CONCATENATED MODULE: ./content-src/components/Search/Search.jsx
  15078 /* This Source Code Form is subject to the terms of the Mozilla Public
  15079 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  15080 * You can obtain one at http://mozilla.org/MPL/2.0/. */
  15081 
  15082 /* globals ContentSearchHandoffUIController */
  15083 
  15084 /**
  15085 * @backward-compat { version 148 }
  15086 *
  15087 * Temporary dual implementation to support train hopping. The old handoff UI
  15088 * is kept alongside the new contentSearchHandoffUI.mjs custom element until
  15089 * the module lands on all channels. Controlled by the pref
  15090 * browser.newtabpage.activity-stream.search.useHandoffComponent.
  15091 * Remove the old implementation and the pref once this ships to Release.
  15092 */
  15093 
  15094 
  15095 
  15096 
  15097 
  15098 
  15099 class _Search extends (external_React_default()).PureComponent {
  15100  constructor(props) {
  15101    super(props);
  15102    this.onSearchHandoffClick = this.onSearchHandoffClick.bind(this);
  15103    this.onSearchHandoffPaste = this.onSearchHandoffPaste.bind(this);
  15104    this.onSearchHandoffDrop = this.onSearchHandoffDrop.bind(this);
  15105    this.onInputMountHandoff = this.onInputMountHandoff.bind(this);
  15106    this.onSearchHandoffButtonMount = this.onSearchHandoffButtonMount.bind(this);
  15107  }
  15108  handleEvent(event) {
  15109    // Also track search events with our own telemetry
  15110    if (event.detail.type === "Search") {
  15111      this.props.dispatch(actionCreators.UserEvent({
  15112        event: "SEARCH"
  15113      }));
  15114    }
  15115  }
  15116  doSearchHandoff(text) {
  15117    this.props.dispatch(actionCreators.OnlyToMain({
  15118      type: actionTypes.HANDOFF_SEARCH_TO_AWESOMEBAR,
  15119      data: {
  15120        text
  15121      }
  15122    }));
  15123    this.props.dispatch({
  15124      type: actionTypes.FAKE_FOCUS_SEARCH
  15125    });
  15126    this.props.dispatch(actionCreators.UserEvent({
  15127      event: "SEARCH_HANDOFF"
  15128    }));
  15129    if (text) {
  15130      this.props.dispatch({
  15131        type: actionTypes.DISABLE_SEARCH
  15132      });
  15133    }
  15134  }
  15135  onSearchHandoffClick(event) {
  15136    // When search hand-off is enabled, we render a big button that is styled to
  15137    // look like a search textbox. If the button is clicked, we style
  15138    // the button as if it was a focused search box and show a fake cursor but
  15139    // really focus the awesomebar without the focus styles ("hidden focus").
  15140    event.preventDefault();
  15141    this.doSearchHandoff();
  15142  }
  15143  onSearchHandoffPaste(event) {
  15144    event.preventDefault();
  15145    this.doSearchHandoff(event.clipboardData.getData("Text"));
  15146  }
  15147  onSearchHandoffDrop(event) {
  15148    event.preventDefault();
  15149    let text = event.dataTransfer.getData("text");
  15150    if (text) {
  15151      this.doSearchHandoff(text);
  15152    }
  15153  }
  15154  componentDidMount() {
  15155    const {
  15156      caretBlinkCount,
  15157      caretBlinkTime,
  15158      "search.useHandoffComponent": useHandoffComponent,
  15159      "externalComponents.enabled": useExternalComponents
  15160    } = this.props.Prefs.values;
  15161    if (useExternalComponents) {
  15162      // Nothing to do - the external component will have set the caret
  15163      // values itself.
  15164      return;
  15165    }
  15166    if (useHandoffComponent) {
  15167      const {
  15168        handoffUI
  15169      } = this;
  15170      if (handoffUI) {
  15171        // If caret blink count isn't defined, use the default infinite behavior for animation
  15172        handoffUI.style.setProperty("--caret-blink-count", caretBlinkCount > -1 ? caretBlinkCount : "infinite");
  15173 
  15174        // Apply custom blink rate if set, else fallback to default (567ms on/off --> 1134ms total)
  15175        handoffUI.style.setProperty("--caret-blink-time", caretBlinkTime > 0 ? `${caretBlinkTime * 2}ms` : `${1134}ms`);
  15176      }
  15177    } else {
  15178      const caret = this.fakeCaret;
  15179      if (caret) {
  15180        // If caret blink count isn't defined, use the default infinite behavior for animation
  15181        caret.style.setProperty("--caret-blink-count", caretBlinkCount > -1 ? caretBlinkCount : "infinite");
  15182 
  15183        // Apply custom blink rate if set, else fallback to default (567ms on/off --> 1134ms total)
  15184        caret.style.setProperty("--caret-blink-time", caretBlinkTime > 0 ? `${caretBlinkTime * 2}ms` : `${1134}ms`);
  15185      }
  15186    }
  15187  }
  15188  onInputMountHandoff(input) {
  15189    if (input) {
  15190      // The handoff UI controller helps us set the search icon and reacts to
  15191      // changes to default engine to keep everything in sync.
  15192      this._handoffSearchController = new ContentSearchHandoffUIController();
  15193    }
  15194  }
  15195  onSearchHandoffButtonMount(button) {
  15196    // Keep a reference to the button for use during "paste" event handling.
  15197    this._searchHandoffButton = button;
  15198  }
  15199 
  15200  /*
  15201   * Do not change the ID on the input field, as legacy newtab code
  15202   * specifically looks for the id 'newtab-search-text' on input fields
  15203   * in order to execute searches in various tests
  15204   */
  15205  render() {
  15206    const useHandoffComponent = this.props.Prefs.values["search.useHandoffComponent"];
  15207    const useExternalComponents = this.props.Prefs.values["externalComponents.enabled"];
  15208    if (useHandoffComponent) {
  15209      if (useExternalComponents) {
  15210        return /*#__PURE__*/external_React_default().createElement("div", {
  15211          className: "search-wrapper"
  15212        }, this.props.showLogo && /*#__PURE__*/external_React_default().createElement(Logo, null), /*#__PURE__*/external_React_default().createElement(ExternalComponentWrapper, {
  15213          type: "SEARCH",
  15214          className: "search-inner-wrapper"
  15215        }));
  15216      }
  15217      return /*#__PURE__*/external_React_default().createElement("div", {
  15218        className: "search-wrapper"
  15219      }, this.props.showLogo && /*#__PURE__*/external_React_default().createElement(Logo, null), /*#__PURE__*/external_React_default().createElement("div", {
  15220        className: "search-inner-wrapper"
  15221      }, /*#__PURE__*/external_React_default().createElement("content-search-handoff-ui", {
  15222        ref: el => {
  15223          this.handoffUI = el;
  15224        }
  15225      })));
  15226    }
  15227    const wrapperClassName = ["search-wrapper", this.props.disable && "search-disabled", this.props.fakeFocus && "fake-focus"].filter(v => v).join(" ");
  15228    return /*#__PURE__*/external_React_default().createElement("div", {
  15229      className: wrapperClassName
  15230    }, this.props.showLogo && /*#__PURE__*/external_React_default().createElement(Logo, null), /*#__PURE__*/external_React_default().createElement("div", {
  15231      className: "search-inner-wrapper"
  15232    }, /*#__PURE__*/external_React_default().createElement("button", {
  15233      className: "search-handoff-button",
  15234      ref: this.onSearchHandoffButtonMount,
  15235      onClick: this.onSearchHandoffClick,
  15236      tabIndex: "-1"
  15237    }, /*#__PURE__*/external_React_default().createElement("div", {
  15238      className: "fake-textbox"
  15239    }), /*#__PURE__*/external_React_default().createElement("input", {
  15240      type: "search",
  15241      className: "fake-editable",
  15242      tabIndex: "-1",
  15243      "aria-hidden": "true",
  15244      onDrop: this.onSearchHandoffDrop,
  15245      onPaste: this.onSearchHandoffPaste,
  15246      ref: this.onInputMountHandoff
  15247    }), /*#__PURE__*/external_React_default().createElement("div", {
  15248      className: "fake-caret",
  15249      ref: el => {
  15250        this.fakeCaret = el;
  15251      }
  15252    }))));
  15253  }
  15254 }
  15255 const Search_Search = (0,external_ReactRedux_namespaceObject.connect)(state => ({
  15256  Prefs: state.Prefs
  15257 }))(_Search);
  15258 ;// CONCATENATED MODULE: ./content-src/components/DownloadModalToggle/DownloadModalToggle.jsx
  15259 /* This Source Code Form is subject to the terms of the Mozilla Public
  15260 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  15261 * You can obtain one at http://mozilla.org/MPL/2.0/. */
  15262 
  15263 
  15264 function DownloadModalToggle({
  15265  onClick,
  15266  isActive
  15267 }) {
  15268  return /*#__PURE__*/external_React_default().createElement("button", {
  15269    className: `mobile-download-promo ${isActive ? " is-active" : ""}`,
  15270    onClick: onClick
  15271  }, /*#__PURE__*/external_React_default().createElement("div", {
  15272    className: "icon icon-device-phone"
  15273  }));
  15274 }
  15275 
  15276 ;// CONCATENATED MODULE: ./content-src/components/Notifications/Toasts/ReportContentToast.jsx
  15277 /* This Source Code Form is subject to the terms of the Mozilla Public
  15278 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  15279 * You can obtain one at http://mozilla.org/MPL/2.0/. */
  15280 
  15281 
  15282 function ReportContentToast({
  15283  onDismissClick,
  15284  onAnimationEnd
  15285 }) {
  15286  const mozMessageBarRef = (0,external_React_namespaceObject.useRef)(null);
  15287  (0,external_React_namespaceObject.useEffect)(() => {
  15288    const {
  15289      current: mozMessageBarElement
  15290    } = mozMessageBarRef;
  15291    mozMessageBarElement.addEventListener("message-bar:user-dismissed", onDismissClick, {
  15292      once: true
  15293    });
  15294    return () => {
  15295      mozMessageBarElement.removeEventListener("message-bar:user-dismissed", onDismissClick);
  15296    };
  15297  }, [onDismissClick]);
  15298  return /*#__PURE__*/external_React_default().createElement("moz-message-bar", {
  15299    type: "success",
  15300    class: "notification-feed-item",
  15301    dismissable: true,
  15302    "data-l10n-id": "newtab-toast-thanks-for-reporting",
  15303    ref: mozMessageBarRef,
  15304    onAnimationEnd: onAnimationEnd
  15305  });
  15306 }
  15307 
  15308 ;// CONCATENATED MODULE: ./content-src/components/Notifications/Notifications.jsx
  15309 /* This Source Code Form is subject to the terms of the Mozilla Public
  15310 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  15311 * You can obtain one at http://mozilla.org/MPL/2.0/. */
  15312 
  15313 
  15314 
  15315 
  15316 
  15317 function Notifications_Notifications({
  15318  dispatch
  15319 }) {
  15320  const toastQueue = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.Notifications.toastQueue);
  15321  const toastCounter = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.Notifications.toastCounter);
  15322 
  15323  /**
  15324   * Syncs {@link toastQueue} array so it can be used to
  15325   * remove the toasts wrapper if there are none after a
  15326   * toast is auto-hidden (animated out) via CSS.
  15327   */
  15328  const syncHiddenToastData = (0,external_React_namespaceObject.useCallback)(() => {
  15329    const toastId = toastQueue[toastQueue.length - 1];
  15330    const queuedToasts = [...toastQueue].slice(1);
  15331    dispatch(actionCreators.OnlyToOneContent({
  15332      type: actionTypes.HIDE_TOAST_MESSAGE,
  15333      data: {
  15334        toastQueue: queuedToasts,
  15335        toastCounter: queuedToasts.length,
  15336        toastId,
  15337        showNotifications: false
  15338      }
  15339    }, "ActivityStream:Content"));
  15340  }, [dispatch, toastQueue]);
  15341  const getToast = (0,external_React_namespaceObject.useCallback)(() => {
  15342    // Note: This architecture could expand to support multiple toast notifications at once
  15343    const latestToastItem = toastQueue[toastQueue.length - 1];
  15344    if (!latestToastItem) {
  15345      throw new Error("No toast found");
  15346    }
  15347    switch (latestToastItem) {
  15348      case "reportSuccessToast":
  15349        return /*#__PURE__*/external_React_default().createElement(ReportContentToast, {
  15350          onDismissClick: syncHiddenToastData,
  15351          onAnimationEnd: syncHiddenToastData,
  15352          key: toastCounter
  15353        });
  15354      default:
  15355        throw new Error(`Unexpected toast type: ${latestToastItem}`);
  15356    }
  15357  }, [syncHiddenToastData, toastCounter, toastQueue]);
  15358  (0,external_React_namespaceObject.useEffect)(() => {
  15359    getToast();
  15360  }, [toastQueue, getToast]);
  15361  return toastQueue.length ? /*#__PURE__*/external_React_default().createElement("div", {
  15362    className: "notification-wrapper"
  15363  }, getToast()) : "";
  15364 }
  15365 
  15366 ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/TopicSelection/TopicSelection.jsx
  15367 /* This Source Code Form is subject to the terms of the Mozilla Public
  15368 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  15369 * You can obtain one at http://mozilla.org/MPL/2.0/. */
  15370 
  15371 
  15372 
  15373 
  15374 
  15375 const EMOJI_LABELS = {
  15376  business: "💼",
  15377  arts: "🎭",
  15378  food: "🍕",
  15379  health: "🩺",
  15380  finance: "💰",
  15381  government: "🏛️",
  15382  sports: "⚽️",
  15383  tech: "💻",
  15384  travel: "✈️",
  15385  "education-science": "🧪",
  15386  society: "💡"
  15387 };
  15388 function TopicSelection({
  15389  supportUrl
  15390 }) {
  15391  const dispatch = (0,external_ReactRedux_namespaceObject.useDispatch)();
  15392  const inputRef = (0,external_React_namespaceObject.useRef)(null);
  15393  const modalRef = (0,external_React_namespaceObject.useRef)(null);
  15394  const checkboxWrapperRef = (0,external_React_namespaceObject.useRef)(null);
  15395  const prefs = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.Prefs.values);
  15396  const topics = prefs["discoverystream.topicSelection.topics"].split(", ");
  15397  const selectedTopics = prefs["discoverystream.topicSelection.selectedTopics"];
  15398  const suggestedTopics = prefs["discoverystream.topicSelection.suggestedTopics"]?.split(", ");
  15399  const displayCount = prefs["discoverystream.topicSelection.onboarding.displayCount"];
  15400  const topicsHaveBeenPreviouslySet = prefs["discoverystream.topicSelection.hasBeenUpdatedPreviously"];
  15401  const [isFirstRun] = (0,external_React_namespaceObject.useState)(displayCount === 0);
  15402  const displayCountRef = (0,external_React_namespaceObject.useRef)(displayCount);
  15403  const preselectedTopics = () => {
  15404    if (selectedTopics) {
  15405      return selectedTopics.split(", ");
  15406    }
  15407    return isFirstRun ? suggestedTopics : [];
  15408  };
  15409  const [topicsToSelect, setTopicsToSelect] = (0,external_React_namespaceObject.useState)(preselectedTopics);
  15410  function isFirstSave() {
  15411    // Only return true if the user has not previous set prefs
  15412    // and the selected topics pref is empty
  15413    if (selectedTopics === "" && !topicsHaveBeenPreviouslySet) {
  15414      return true;
  15415    }
  15416    return false;
  15417  }
  15418  function handleModalClose() {
  15419    dispatch(actionCreators.OnlyToMain({
  15420      type: actionTypes.TOPIC_SELECTION_USER_DISMISS
  15421    }));
  15422    dispatch(actionCreators.BroadcastToContent({
  15423      type: actionTypes.TOPIC_SELECTION_SPOTLIGHT_CLOSE
  15424    }));
  15425  }
  15426  function handleUserClose(e) {
  15427    const id = e?.target?.id;
  15428    if (id === "first-run") {
  15429      dispatch(actionCreators.AlsoToMain({
  15430        type: actionTypes.TOPIC_SELECTION_MAYBE_LATER
  15431      }));
  15432      dispatch(actionCreators.SetPref("discoverystream.topicSelection.onboarding.maybeDisplay", true));
  15433    } else {
  15434      dispatch(actionCreators.SetPref("discoverystream.topicSelection.onboarding.maybeDisplay", false));
  15435    }
  15436    handleModalClose();
  15437  }
  15438 
  15439  // By doing this, the useEffect that sets up the IntersectionObserver
  15440  // will not re-run every time displayCount changes,
  15441  // but the observer callback will always have access
  15442  // to the latest displayCount value through the ref.
  15443  (0,external_React_namespaceObject.useEffect)(() => {
  15444    displayCountRef.current = displayCount;
  15445  }, [displayCount]);
  15446  (0,external_React_namespaceObject.useEffect)(() => {
  15447    const {
  15448      current
  15449    } = modalRef;
  15450    let observer;
  15451    if (current) {
  15452      observer = new IntersectionObserver(([entry]) => {
  15453        if (entry.isIntersecting) {
  15454          // if the user has seen the modal more than 3 times,
  15455          // automatically remove them from onboarding
  15456          if (displayCountRef.current > 3) {
  15457            dispatch(actionCreators.SetPref("discoverystream.topicSelection.onboarding.maybeDisplay", false));
  15458          }
  15459          observer.unobserve(modalRef.current);
  15460          dispatch(actionCreators.AlsoToMain({
  15461            type: actionTypes.TOPIC_SELECTION_IMPRESSION
  15462          }));
  15463        }
  15464      });
  15465      observer.observe(current);
  15466    }
  15467    return () => {
  15468      if (current) {
  15469        observer.unobserve(current);
  15470      }
  15471    };
  15472  }, [modalRef, dispatch]);
  15473 
  15474  // when component mounts, set focus to input
  15475  (0,external_React_namespaceObject.useEffect)(() => {
  15476    inputRef?.current?.focus();
  15477  }, [inputRef]);
  15478  const handleFocus = (0,external_React_namespaceObject.useCallback)(e => {
  15479    // this list will have to be updated with other reusable components that get used inside of this modal
  15480    const tabbableElements = modalRef.current.querySelectorAll('a[href], button, moz-button, input[tabindex="0"]');
  15481    const [firstTabableEl] = tabbableElements;
  15482    const lastTabbableEl = tabbableElements[tabbableElements.length - 1];
  15483    let isTabPressed = e.key === "Tab" || e.keyCode === 9;
  15484    let isArrowPressed = e.key === "ArrowUp" || e.key === "ArrowDown";
  15485    if (isTabPressed) {
  15486      if (e.shiftKey) {
  15487        if (document.activeElement === firstTabableEl) {
  15488          lastTabbableEl.focus();
  15489          e.preventDefault();
  15490        }
  15491      } else if (document.activeElement === lastTabbableEl) {
  15492        firstTabableEl.focus();
  15493        e.preventDefault();
  15494      }
  15495    } else if (isArrowPressed && checkboxWrapperRef.current.contains(document.activeElement)) {
  15496      const checkboxElements = checkboxWrapperRef.current.querySelectorAll("input");
  15497      const [firstInput] = checkboxElements;
  15498      const lastInput = checkboxElements[checkboxElements.length - 1];
  15499      const inputArr = Array.from(checkboxElements);
  15500      const currentIndex = inputArr.indexOf(document.activeElement);
  15501      let nextEl;
  15502      if (e.key === "ArrowUp") {
  15503        nextEl = document.activeElement === firstInput ? lastInput : checkboxElements[currentIndex - 1];
  15504      } else if (e.key === "ArrowDown") {
  15505        nextEl = document.activeElement === lastInput ? firstInput : checkboxElements[currentIndex + 1];
  15506      }
  15507      nextEl.tabIndex = 0;
  15508      document.activeElement.tabIndex = -1;
  15509      nextEl.focus();
  15510    }
  15511  }, []);
  15512  (0,external_React_namespaceObject.useEffect)(() => {
  15513    const ref = modalRef.current;
  15514    ref.addEventListener("keydown", handleFocus);
  15515    inputRef.current.tabIndex = 0;
  15516    return () => {
  15517      ref.removeEventListener("keydown", handleFocus);
  15518    };
  15519  }, [handleFocus]);
  15520  function handleChange(e) {
  15521    const topic = e.target.name;
  15522    const isChecked = e.target.checked;
  15523    if (isChecked) {
  15524      setTopicsToSelect([...topicsToSelect, topic]);
  15525    } else {
  15526      const updatedTopics = topicsToSelect.filter(t => t !== topic);
  15527      setTopicsToSelect(updatedTopics);
  15528    }
  15529  }
  15530  function handleSubmit() {
  15531    const topicsString = topicsToSelect.join(", ");
  15532    dispatch(actionCreators.SetPref("discoverystream.topicSelection.selectedTopics", topicsString));
  15533    dispatch(actionCreators.SetPref("discoverystream.topicSelection.onboarding.maybeDisplay", false));
  15534    if (!topicsHaveBeenPreviouslySet) {
  15535      dispatch(actionCreators.SetPref("discoverystream.topicSelection.hasBeenUpdatedPreviously", true));
  15536    }
  15537    dispatch(actionCreators.OnlyToMain({
  15538      type: actionTypes.TOPIC_SELECTION_USER_SAVE,
  15539      data: {
  15540        topics: topicsString,
  15541        previous_topics: selectedTopics,
  15542        first_save: isFirstSave()
  15543      }
  15544    }));
  15545    handleModalClose();
  15546  }
  15547  return /*#__PURE__*/external_React_default().createElement(ModalOverlayWrapper, {
  15548    onClose: handleUserClose,
  15549    innerClassName: "topic-selection-container"
  15550  }, /*#__PURE__*/external_React_default().createElement("div", {
  15551    className: "topic-selection-form",
  15552    ref: modalRef
  15553  }, /*#__PURE__*/external_React_default().createElement("button", {
  15554    className: "dismiss-button",
  15555    title: "dismiss",
  15556    onClick: handleUserClose
  15557  }), /*#__PURE__*/external_React_default().createElement("h1", {
  15558    className: "title",
  15559    "data-l10n-id": "newtab-topic-selection-title"
  15560  }), /*#__PURE__*/external_React_default().createElement("p", {
  15561    className: "subtitle",
  15562    "data-l10n-id": "newtab-topic-selection-subtitle"
  15563  }), /*#__PURE__*/external_React_default().createElement("div", {
  15564    className: "topic-list",
  15565    ref: checkboxWrapperRef
  15566  }, topics.map((topic, i) => {
  15567    const checked = topicsToSelect.includes(topic);
  15568    return /*#__PURE__*/external_React_default().createElement("label", {
  15569      className: `topic-item`,
  15570      key: topic
  15571    }, /*#__PURE__*/external_React_default().createElement("input", {
  15572      type: "checkbox",
  15573      id: topic,
  15574      name: topic,
  15575      ref: i === 0 ? inputRef : null,
  15576      onChange: handleChange,
  15577      checked: checked,
  15578      "aria-checked": checked,
  15579      tabIndex: -1
  15580    }), /*#__PURE__*/external_React_default().createElement("div", {
  15581      className: `topic-custom-checkbox`
  15582    }, /*#__PURE__*/external_React_default().createElement("span", {
  15583      className: "topic-icon"
  15584    }, EMOJI_LABELS[`${topic}`]), /*#__PURE__*/external_React_default().createElement("span", {
  15585      className: "topic-checked"
  15586    })), /*#__PURE__*/external_React_default().createElement("span", {
  15587      className: "topic-item-label",
  15588      "data-l10n-id": `newtab-topic-label-${topic}`
  15589    }));
  15590  })), /*#__PURE__*/external_React_default().createElement("div", {
  15591    className: "modal-footer"
  15592  }, /*#__PURE__*/external_React_default().createElement("a", {
  15593    href: supportUrl,
  15594    "data-l10n-id": "newtab-topic-selection-privacy-link"
  15595  }), /*#__PURE__*/external_React_default().createElement("moz-button-group", {
  15596    className: "button-group"
  15597  }, /*#__PURE__*/external_React_default().createElement("moz-button", {
  15598    id: isFirstRun ? "first-run" : "",
  15599    "data-l10n-id": isFirstRun ? "newtab-topic-selection-button-maybe-later" : "newtab-topic-selection-cancel-button",
  15600    onClick: handleUserClose
  15601  }), /*#__PURE__*/external_React_default().createElement("moz-button", {
  15602    "data-l10n-id": "newtab-topic-selection-save-button",
  15603    type: "primary",
  15604    onClick: handleSubmit
  15605  })))));
  15606 }
  15607 
  15608 ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/FeatureHighlight/DownloadMobilePromoHighlight.jsx
  15609 /* This Source Code Form is subject to the terms of the Mozilla Public
  15610 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  15611 * You can obtain one at http://mozilla.org/MPL/2.0/. */
  15612 
  15613 
  15614 
  15615 
  15616 
  15617 const PREF_MOBILE_DOWNLOAD_HIGHLIGHT_VARIANT_A = "mobileDownloadModal.variant-a";
  15618 const PREF_MOBILE_DOWNLOAD_HIGHLIGHT_VARIANT_B = "mobileDownloadModal.variant-b";
  15619 const PREF_MOBILE_DOWNLOAD_HIGHLIGHT_VARIANT_C = "mobileDownloadModal.variant-c";
  15620 const FEATURE_ID = "FEATURE_DOWNLOAD_MOBILE_PROMO";
  15621 function DownloadMobilePromoHighlight({
  15622  position,
  15623  dispatch,
  15624  handleDismiss,
  15625  handleBlock,
  15626  isIntersecting
  15627 }) {
  15628  const onDismiss = (0,external_React_namespaceObject.useCallback)(() => {
  15629    // This event is emitted manually because the feature may be triggered outside the OMC flow,
  15630    // and may not be captured by the messaging-system’s automatic reporting.
  15631    dispatch(actionCreators.DiscoveryStreamUserEvent({
  15632      event: "FEATURE_HIGHLIGHT_DISMISS",
  15633      source: "FEATURE_HIGHLIGHT",
  15634      value: {
  15635        feature: FEATURE_ID
  15636      }
  15637    }));
  15638    handleDismiss();
  15639    handleBlock();
  15640  }, [dispatch, handleDismiss, handleBlock]);
  15641  (0,external_React_namespaceObject.useEffect)(() => {
  15642    if (isIntersecting) {
  15643      // This event is emitted manually because the feature may be triggered outside the OMC flow,
  15644      // and may not be captured by the messaging-system’s automatic reporting.
  15645      dispatch(actionCreators.DiscoveryStreamUserEvent({
  15646        event: "FEATURE_HIGHLIGHT_IMPRESSION",
  15647        source: "FEATURE_HIGHLIGHT",
  15648        value: {
  15649          feature: FEATURE_ID
  15650        }
  15651      }));
  15652    }
  15653  }, [dispatch, isIntersecting]);
  15654  const prefs = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.Prefs.values);
  15655  const mobileDownloadPromoVarA = prefs[PREF_MOBILE_DOWNLOAD_HIGHLIGHT_VARIANT_A];
  15656  const mobileDownloadPromoVarB = prefs[PREF_MOBILE_DOWNLOAD_HIGHLIGHT_VARIANT_B];
  15657  const mobileDownloadPromoVarC = prefs[PREF_MOBILE_DOWNLOAD_HIGHLIGHT_VARIANT_C];
  15658  function getActiveVariant() {
  15659    if (mobileDownloadPromoVarA) {
  15660      return "A";
  15661    }
  15662    if (mobileDownloadPromoVarB) {
  15663      return "B";
  15664    }
  15665    if (mobileDownloadPromoVarC) {
  15666      return "C";
  15667    }
  15668    return null;
  15669  }
  15670  function getVariantQRCodeImg() {
  15671    const variant = getActiveVariant();
  15672    switch (variant) {
  15673      case "A":
  15674        return "chrome://newtab/content/data/content/assets/download-qr-code-var-a.png";
  15675      case "B":
  15676        return "chrome://newtab/content/data/content/assets/download-qr-code-var-b.png";
  15677      case "C":
  15678        return "chrome://newtab/content/data/content/assets/download-qr-code-var-c.png";
  15679      default:
  15680        return null;
  15681    }
  15682  }
  15683  function getVariantCopy() {
  15684    const variant = getActiveVariant();
  15685    switch (variant) {
  15686      case "A":
  15687        return "newtab-download-mobile-highlight-body-variant-a";
  15688      case "B":
  15689        return "newtab-download-mobile-highlight-body-variant-b";
  15690      case "C":
  15691        return "newtab-download-mobile-highlight-body-variant-c";
  15692      default:
  15693        return null;
  15694    }
  15695  }
  15696  return /*#__PURE__*/external_React_default().createElement("div", {
  15697    className: "download-firefox-feature-highlight"
  15698  }, /*#__PURE__*/external_React_default().createElement(FeatureHighlight, {
  15699    position: position,
  15700    feature: FEATURE_ID,
  15701    dispatch: dispatch,
  15702    message: /*#__PURE__*/external_React_default().createElement("div", {
  15703      className: "download-firefox-feature-highlight-content"
  15704    }, /*#__PURE__*/external_React_default().createElement("img", {
  15705      src: getVariantQRCodeImg(),
  15706      "data-l10n-id": "newtab-download-mobile-highlight-image",
  15707      width: "120",
  15708      height: "191",
  15709      alt: ""
  15710    }), /*#__PURE__*/external_React_default().createElement("p", {
  15711      className: "title",
  15712      "data-l10n-id": "newtab-download-mobile-highlight-title"
  15713    }), /*#__PURE__*/external_React_default().createElement("p", {
  15714      className: "subtitle",
  15715      "data-l10n-id": getVariantCopy()
  15716    })),
  15717    openedOverride: true,
  15718    showButtonIcon: false,
  15719    dismissCallback: onDismiss,
  15720    outsideClickCallback: handleDismiss
  15721  }));
  15722 }
  15723 ;// CONCATENATED MODULE: ./content-src/components/DiscoveryStreamComponents/FeatureHighlight/WallpaperFeatureHighlight.jsx
  15724 /* This Source Code Form is subject to the terms of the Mozilla Public
  15725 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  15726 * You can obtain one at http://mozilla.org/MPL/2.0/. */
  15727 
  15728 
  15729 
  15730 
  15731 
  15732 function WallpaperFeatureHighlight({
  15733  position,
  15734  dispatch,
  15735  handleDismiss,
  15736  handleClick,
  15737  handleBlock
  15738 }) {
  15739  const onDismiss = (0,external_React_namespaceObject.useCallback)(() => {
  15740    handleDismiss();
  15741    handleBlock();
  15742  }, [handleDismiss, handleBlock]);
  15743  const onToggleClick = (0,external_React_namespaceObject.useCallback)(elementId => {
  15744    dispatch({
  15745      type: actionTypes.SHOW_PERSONALIZE
  15746    });
  15747    dispatch(actionCreators.UserEvent({
  15748      event: "SHOW_PERSONALIZE"
  15749    }));
  15750    handleClick(elementId);
  15751    onDismiss();
  15752  }, [dispatch, onDismiss, handleClick]);
  15753 
  15754  // Extract the strings and feature ID from OMC
  15755  const {
  15756    messageData
  15757  } = (0,external_ReactRedux_namespaceObject.useSelector)(state => state.Messages);
  15758  return /*#__PURE__*/external_React_default().createElement("div", {
  15759    className: `wallpaper-feature-highlight ${messageData.content?.darkModeDismiss ? "is-inverted-dark-dismiss-button" : ""}`
  15760  }, /*#__PURE__*/external_React_default().createElement(FeatureHighlight, {
  15761    position: position,
  15762    "data-l10n-id": "feature-highlight-wallpaper",
  15763    feature: messageData.content.feature,
  15764    dispatch: dispatch,
  15765    message: /*#__PURE__*/external_React_default().createElement("div", {
  15766      className: "wallpaper-feature-highlight-content"
  15767    }, /*#__PURE__*/external_React_default().createElement("picture", {
  15768      className: "follow-section-button-highlight-image"
  15769    }, /*#__PURE__*/external_React_default().createElement("source", {
  15770      srcSet: messageData.content?.darkModeImageURL || "chrome://newtab/content/data/content/assets/highlights/omc-newtab-wallpapers.svg",
  15771      media: "(prefers-color-scheme: dark)"
  15772    }), /*#__PURE__*/external_React_default().createElement("source", {
  15773      srcSet: messageData.content?.imageURL || "chrome://newtab/content/data/content/assets/highlights/omc-newtab-wallpapers.svg",
  15774      media: "(prefers-color-scheme: light)"
  15775    }), /*#__PURE__*/external_React_default().createElement("img", {
  15776      width: "320",
  15777      height: "195",
  15778      alt: ""
  15779    })), messageData.content?.cardTitle ? /*#__PURE__*/external_React_default().createElement("p", {
  15780      className: "title"
  15781    }, messageData.content.cardTitle) : /*#__PURE__*/external_React_default().createElement("p", {
  15782      className: "title",
  15783      "data-l10n-id": messageData.content.title || "newtab-new-user-custom-wallpaper-title"
  15784    }), messageData.content?.cardMessage ? /*#__PURE__*/external_React_default().createElement("p", {
  15785      className: "subtitle"
  15786    }, messageData.content.cardMessage) : /*#__PURE__*/external_React_default().createElement("p", {
  15787      className: "subtitle",
  15788      "data-l10n-id": messageData.content.subtitle || "newtab-new-user-custom-wallpaper-subtitle"
  15789    }), /*#__PURE__*/external_React_default().createElement("span", {
  15790      className: "button-wrapper"
  15791    }, messageData.content?.cardCta ? /*#__PURE__*/external_React_default().createElement("moz-button", {
  15792      type: "default",
  15793      onClick: () => onToggleClick("open-customize-menu"),
  15794      label: messageData.content.cardCta
  15795    }) : /*#__PURE__*/external_React_default().createElement("moz-button", {
  15796      type: "default",
  15797      onClick: () => onToggleClick("open-customize-menu"),
  15798      "data-l10n-id": messageData.content.cta || "newtab-new-user-custom-wallpaper-cta"
  15799    }))),
  15800    toggle: /*#__PURE__*/external_React_default().createElement("div", {
  15801      className: "icon icon-help"
  15802    }),
  15803    openedOverride: true,
  15804    showButtonIcon: false,
  15805    dismissCallback: onDismiss,
  15806    outsideClickCallback: handleDismiss
  15807  }));
  15808 }
  15809 ;// CONCATENATED MODULE: ./content-src/components/Base/Base.jsx
  15810 function Base_extends() { return Base_extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, Base_extends.apply(null, arguments); }
  15811 /* This Source Code Form is subject to the terms of the Mozilla Public
  15812 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  15813 * You can obtain one at http://mozilla.org/MPL/2.0/. */
  15814 
  15815 
  15816 
  15817 
  15818 
  15819 
  15820 
  15821 
  15822 
  15823 
  15824 
  15825 
  15826 
  15827 
  15828 
  15829 
  15830 
  15831 
  15832 
  15833 
  15834 const Base_VISIBLE = "visible";
  15835 const Base_VISIBILITY_CHANGE_EVENT = "visibilitychange";
  15836 const PREF_INFERRED_PERSONALIZATION_SYSTEM = "discoverystream.sections.personalization.inferred.enabled";
  15837 const Base_PREF_INFERRED_PERSONALIZATION_USER = "discoverystream.sections.personalization.inferred.user.enabled";
  15838 
  15839 // Returns a function will not be continuously triggered when called. The
  15840 // function will be triggered if called again after `wait` milliseconds.
  15841 function Base_debounce(func, wait) {
  15842  let timer;
  15843  return (...args) => {
  15844    if (timer) {
  15845      return;
  15846    }
  15847    let wakeUp = () => {
  15848      timer = null;
  15849    };
  15850    timer = setTimeout(wakeUp, wait);
  15851    func.apply(this, args);
  15852  };
  15853 }
  15854 function WithDsAdmin(props) {
  15855  const {
  15856    hash = globalThis?.location?.hash || ""
  15857  } = props;
  15858  const [devtoolsCollapsed, setDevtoolsCollapsed] = (0,external_React_namespaceObject.useState)(!hash.startsWith("#devtools"));
  15859  (0,external_React_namespaceObject.useEffect)(() => {
  15860    const onHashChange = () => {
  15861      const h = globalThis?.location?.hash || "";
  15862      setDevtoolsCollapsed(!h.startsWith("#devtools"));
  15863    };
  15864 
  15865    // run once in case hash changed before mount
  15866    onHashChange();
  15867    globalThis?.addEventListener("hashchange", onHashChange);
  15868    return () => globalThis?.removeEventListener("hashchange", onHashChange);
  15869  }, []);
  15870  return /*#__PURE__*/external_React_default().createElement((external_React_default()).Fragment, null, /*#__PURE__*/external_React_default().createElement(DiscoveryStreamAdmin, {
  15871    devtoolsCollapsed: devtoolsCollapsed
  15872  }), devtoolsCollapsed ? /*#__PURE__*/external_React_default().createElement(BaseContent, props) : null);
  15873 }
  15874 function _Base(props) {
  15875  const isDevtoolsEnabled = props.Prefs.values["asrouter.devtoolsEnabled"];
  15876  const {
  15877    App
  15878  } = props;
  15879  if (!App.initialized) {
  15880    return null;
  15881  }
  15882  return /*#__PURE__*/external_React_default().createElement(ErrorBoundary, {
  15883    className: "base-content-fallback"
  15884  }, isDevtoolsEnabled ? /*#__PURE__*/external_React_default().createElement(WithDsAdmin, props) : /*#__PURE__*/external_React_default().createElement(BaseContent, props));
  15885 }
  15886 class BaseContent extends (external_React_default()).PureComponent {
  15887  constructor(props) {
  15888    super(props);
  15889    this.openPreferences = this.openPreferences.bind(this);
  15890    this.openCustomizationMenu = this.openCustomizationMenu.bind(this);
  15891    this.closeCustomizationMenu = this.closeCustomizationMenu.bind(this);
  15892    this.handleOnKeyDown = this.handleOnKeyDown.bind(this);
  15893    this.onWindowScroll = Base_debounce(this.onWindowScroll.bind(this), 5);
  15894    this.setPref = this.setPref.bind(this);
  15895    this.shouldShowOMCHighlight = this.shouldShowOMCHighlight.bind(this);
  15896    this.updateWallpaper = this.updateWallpaper.bind(this);
  15897    this.prefersDarkQuery = null;
  15898    this.handleColorModeChange = this.handleColorModeChange.bind(this);
  15899    this.onVisible = this.onVisible.bind(this);
  15900    this.toggleDownloadHighlight = this.toggleDownloadHighlight.bind(this);
  15901    this.handleDismissDownloadHighlight = this.handleDismissDownloadHighlight.bind(this);
  15902    this.applyBodyClasses = this.applyBodyClasses.bind(this);
  15903    this.toggleSectionsMgmtPanel = this.toggleSectionsMgmtPanel.bind(this);
  15904    this.state = {
  15905      fixedSearch: false,
  15906      firstVisibleTimestamp: null,
  15907      colorMode: "",
  15908      fixedNavStyle: {},
  15909      wallpaperTheme: "",
  15910      showDownloadHighlightOverride: null,
  15911      visible: false,
  15912      showSectionsMgmtPanel: false
  15913    };
  15914    this.spocPlaceholderStartTime = null;
  15915  }
  15916  setFirstVisibleTimestamp() {
  15917    if (!this.state.firstVisibleTimestamp) {
  15918      this.setState({
  15919        firstVisibleTimestamp: Date.now()
  15920      });
  15921    }
  15922  }
  15923  onVisible() {
  15924    this.setState({
  15925      visible: true
  15926    });
  15927    this.setFirstVisibleTimestamp();
  15928    this.shouldDisplayTopicSelectionModal();
  15929    this.onVisibilityDispatch();
  15930    if (this.isSpocsOnDemandExpired && !this.spocPlaceholderStartTime) {
  15931      this.spocPlaceholderStartTime = Date.now();
  15932    }
  15933  }
  15934  onVisibilityDispatch() {
  15935    const {
  15936      onDemand = {}
  15937    } = this.props.DiscoveryStream.spocs;
  15938 
  15939    // We only need to dispatch this if:
  15940    // 1. onDemand is enabled,
  15941    // 2. onDemand spocs have not been loaded on this tab.
  15942    // 3. Spocs are expired.
  15943    if (onDemand.enabled && !onDemand.loaded && this.isSpocsOnDemandExpired) {
  15944      // This dispatches that spocs are expired and we need to update them.
  15945      this.props.dispatch(actionCreators.OnlyToMain({
  15946        type: actionTypes.DISCOVERY_STREAM_SPOCS_ONDEMAND_UPDATE
  15947      }));
  15948    }
  15949  }
  15950  get isSpocsOnDemandExpired() {
  15951    const {
  15952      onDemand = {},
  15953      cacheUpdateTime,
  15954      lastUpdated
  15955    } = this.props.DiscoveryStream.spocs;
  15956 
  15957    // We can bail early if:
  15958    // 1. onDemand is off,
  15959    // 2. onDemand spocs have been loaded on this tab.
  15960    if (!onDemand.enabled || onDemand.loaded) {
  15961      return false;
  15962    }
  15963    return Date.now() - lastUpdated >= cacheUpdateTime;
  15964  }
  15965  spocsOnDemandUpdated() {
  15966    const {
  15967      onDemand = {},
  15968      loaded
  15969    } = this.props.DiscoveryStream.spocs;
  15970 
  15971    // We only need to fire this if:
  15972    // 1. Spoc data is loaded.
  15973    // 2. onDemand is enabled.
  15974    // 3. The component is visible (not preloaded tab).
  15975    // 4. onDemand spocs have not been loaded on this tab.
  15976    // 5. Spocs are not expired.
  15977    if (loaded && onDemand.enabled && this.state.visible && !onDemand.loaded && !this.isSpocsOnDemandExpired) {
  15978      // This dispatches that spocs have been loaded on this tab
  15979      // and we don't need to update them again for this tab.
  15980      this.props.dispatch(actionCreators.BroadcastToContent({
  15981        type: actionTypes.DISCOVERY_STREAM_SPOCS_ONDEMAND_LOAD
  15982      }));
  15983    }
  15984  }
  15985  componentDidMount() {
  15986    this.applyBodyClasses();
  15987    __webpack_require__.g.addEventListener("scroll", this.onWindowScroll);
  15988    __webpack_require__.g.addEventListener("keydown", this.handleOnKeyDown);
  15989    const prefs = this.props.Prefs.values;
  15990    const wallpapersEnabled = prefs["newtabWallpapers.enabled"];
  15991    if (!prefs["externalComponents.enabled"]) {
  15992      if (prefs["search.useHandoffComponent"]) {
  15993        // Dynamically import the contentSearchHandoffUI module, but don't worry
  15994        // about webpacking this one.
  15995        import(/* webpackIgnore: true */"chrome://browser/content/contentSearchHandoffUI.mjs");
  15996      } else {
  15997        const scriptURL = "chrome://browser/content/contentSearchHandoffUI.js";
  15998        const scriptEl = document.createElement("script");
  15999        scriptEl.src = scriptURL;
  16000        document.head.appendChild(scriptEl);
  16001      }
  16002    }
  16003    if (this.props.document.visibilityState === Base_VISIBLE) {
  16004      this.onVisible();
  16005    } else {
  16006      this._onVisibilityChange = () => {
  16007        if (this.props.document.visibilityState === Base_VISIBLE) {
  16008          this.onVisible();
  16009          this.props.document.removeEventListener(Base_VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
  16010          this._onVisibilityChange = null;
  16011        }
  16012      };
  16013      this.props.document.addEventListener(Base_VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
  16014    }
  16015    // track change event to dark/light mode
  16016    this.prefersDarkQuery = globalThis.matchMedia("(prefers-color-scheme: dark)");
  16017    this.prefersDarkQuery.addEventListener("change", this.handleColorModeChange);
  16018    this.handleColorModeChange();
  16019    if (wallpapersEnabled) {
  16020      this.updateWallpaper();
  16021    }
  16022    this._onHashChange = () => {
  16023      const hash = globalThis.location?.hash || "";
  16024      if (hash === "#customize" || hash === "#customize-topics") {
  16025        this.openCustomizationMenu();
  16026        if (hash === "#customize-topics") {
  16027          this.toggleSectionsMgmtPanel();
  16028        }
  16029      } else if (this.props.App.customizeMenuVisible) {
  16030        this.closeCustomizationMenu();
  16031      }
  16032    };
  16033 
  16034    // Using the Performance API to detect page reload vs fresh navigation.
  16035    // Only open customize menu on fresh navigation, not on page refresh.
  16036    // See: https://developer.mozilla.org/en-US/docs/Web/API/Performance/getEntriesByType
  16037    // See: https://developer.mozilla.org/en-US/docs/Web/API/PerformanceEntry/entryType#navigation
  16038    // See: https://developer.mozilla.org/en-US/docs/Web/API/PerformanceNavigationTiming/type
  16039    const isReload = globalThis.performance?.getEntriesByType("navigation")[0]?.type === "reload";
  16040    if (!isReload) {
  16041      this._onHashChange();
  16042    }
  16043    globalThis.addEventListener("hashchange", this._onHashChange);
  16044  }
  16045  componentDidUpdate(prevProps) {
  16046    this.applyBodyClasses();
  16047    const prefs = this.props.Prefs.values;
  16048 
  16049    // Check if weather widget was re-enabled from customization menu
  16050    const wasWeatherDisabled = !prevProps.Prefs.values.showWeather;
  16051    const isWeatherEnabled = this.props.Prefs.values.showWeather;
  16052    if (wasWeatherDisabled && isWeatherEnabled) {
  16053      // If weather widget was enabled from customization menu, display opt-in dialog
  16054      this.props.dispatch(actionCreators.SetPref("weather.optInDisplayed", true));
  16055    }
  16056    const wallpapersEnabled = prefs["newtabWallpapers.enabled"];
  16057    if (wallpapersEnabled) {
  16058      // destructure current and previous props with fallbacks
  16059      // (preventing undefined errors)
  16060      const {
  16061        Wallpapers: {
  16062          uploadedWallpaper = null,
  16063          wallpaperList = null
  16064        } = {}
  16065      } = this.props;
  16066      const {
  16067        Wallpapers: {
  16068          uploadedWallpaper: prevUploadedWallpaper = null,
  16069          wallpaperList: prevWallpaperList = null
  16070        } = {},
  16071        Prefs: {
  16072          values: prevPrefs = {}
  16073        } = {}
  16074      } = prevProps;
  16075      const selectedWallpaper = prefs["newtabWallpapers.wallpaper"];
  16076      const prevSelectedWallpaper = prevPrefs["newtabWallpapers.wallpaper"];
  16077      const uploadedWallpaperTheme = prefs["newtabWallpapers.customWallpaper.theme"];
  16078      const prevUploadedWallpaperTheme = prevPrefs["newtabWallpapers.customWallpaper.theme"];
  16079 
  16080      // don't update wallpaper unless the wallpaper is being changed.
  16081      if (selectedWallpaper !== prevSelectedWallpaper ||
  16082      // selecting a new wallpaper
  16083      uploadedWallpaper !== prevUploadedWallpaper ||
  16084      // uploading a new wallpaper
  16085      wallpaperList !== prevWallpaperList ||
  16086      // remote settings wallpaper list updates
  16087      this.props.App.isForStartupCache.Wallpaper !== prevProps.App.isForStartupCache.Wallpaper ||
  16088      // Startup cached page wallpaper is updating
  16089      uploadedWallpaperTheme !== prevUploadedWallpaperTheme) {
  16090        this.updateWallpaper();
  16091      }
  16092    }
  16093    this.spocsOnDemandUpdated();
  16094    this.trackSpocPlaceholderDuration(prevProps);
  16095  }
  16096  trackSpocPlaceholderDuration(prevProps) {
  16097    // isExpired returns true when the current props have expired spocs (showing placeholders)
  16098    const isExpired = this.isSpocsOnDemandExpired;
  16099 
  16100    // Init tracking when placeholders become visible
  16101    if (isExpired && this.state.visible && !this.spocPlaceholderStartTime) {
  16102      this.spocPlaceholderStartTime = Date.now();
  16103    }
  16104 
  16105    // wasExpired returns true when the previous props had expired spocs (showing placeholders)
  16106    const wasExpired = prevProps.DiscoveryStream.spocs.onDemand?.enabled && !prevProps.DiscoveryStream.spocs.onDemand?.loaded && Date.now() - prevProps.DiscoveryStream.spocs.lastUpdated >= prevProps.DiscoveryStream.spocs.cacheUpdateTime;
  16107 
  16108    // Record duration telemetry event when placeholders are replaced with real content
  16109    if (wasExpired && !isExpired && this.spocPlaceholderStartTime) {
  16110      const duration = Date.now() - this.spocPlaceholderStartTime;
  16111      this.props.dispatch(actionCreators.OnlyToMain({
  16112        type: actionTypes.DISCOVERY_STREAM_SPOC_PLACEHOLDER_DURATION,
  16113        data: {
  16114          duration
  16115        }
  16116      }));
  16117      this.spocPlaceholderStartTime = null;
  16118    }
  16119  }
  16120  handleColorModeChange() {
  16121    const colorMode = this.prefersDarkQuery?.matches ? "dark" : "light";
  16122    if (colorMode !== this.state.colorMode) {
  16123      this.setState({
  16124        colorMode
  16125      });
  16126      this.updateWallpaper();
  16127    }
  16128  }
  16129  componentWillUnmount() {
  16130    this.prefersDarkQuery?.removeEventListener("change", this.handleColorModeChange);
  16131    __webpack_require__.g.removeEventListener("scroll", this.onWindowScroll);
  16132    __webpack_require__.g.removeEventListener("keydown", this.handleOnKeyDown);
  16133    if (this._onVisibilityChange) {
  16134      this.props.document.removeEventListener(Base_VISIBILITY_CHANGE_EVENT, this._onVisibilityChange);
  16135    }
  16136    if (this._onHashChange) {
  16137      globalThis.removeEventListener("hashchange", this._onHashChange);
  16138    }
  16139  }
  16140  onWindowScroll() {
  16141    if (window.innerHeight <= 700) {
  16142      // Bug 1937296: Only apply fixed-search logic
  16143      // if the page is tall enough to support it.
  16144      return;
  16145    }
  16146    const prefs = this.props.Prefs.values;
  16147    const {
  16148      showSearch
  16149    } = prefs;
  16150    if (!showSearch) {
  16151      // Bug 1944718: Only apply fixed-search logic
  16152      // if search is visible.
  16153      return;
  16154    }
  16155    const logoAlwaysVisible = prefs["logowordmark.alwaysVisible"];
  16156 
  16157    /* Bug 1917937: The logic presented below is fragile but accurate to the pixel. As new tab experiments with layouts, we have a tech debt of competing styles and classes the slightly modify where the search bar sits on the page. The larger solution for this is to replace everything with an intersection observer, but would require a larger refactor of this file. In the interim, we can programmatically calculate when to fire the fixed-scroll event and account for the moved elements so that topsites/etc stays in the same place. The CSS this references has been flagged to reference this logic so (hopefully) keep them in sync. */
  16158 
  16159    let SCROLL_THRESHOLD = 0; // When the fixed-scroll event fires
  16160    let MAIN_OFFSET_PADDING = 0; // The padding to compensate for the moved elements
  16161 
  16162    const CSS_VAR_SPACE_XXLARGE = 32.04; // Custom Acorn themed variable (8 * 0.267rem);
  16163 
  16164    let layout = {
  16165      outerWrapperPaddingTop: 32.04,
  16166      searchWrapperPaddingTop: 16.02,
  16167      searchWrapperPaddingBottom: CSS_VAR_SPACE_XXLARGE,
  16168      searchWrapperFixedScrollPaddingTop: 24.03,
  16169      searchWrapperFixedScrollPaddingBottom: 24.03,
  16170      searchInnerWrapperMinHeight: 52,
  16171      logoAndWordmarkWrapperHeight: 0,
  16172      logoAndWordmarkWrapperMarginBottom: 0
  16173    };
  16174 
  16175    // Logo visibility applies to all layouts
  16176    if (!logoAlwaysVisible) {
  16177      layout.logoAndWordmarkWrapperHeight = 0;
  16178      layout.logoAndWordmarkWrapperMarginBottom = 0;
  16179    }
  16180    SCROLL_THRESHOLD = layout.outerWrapperPaddingTop + layout.searchWrapperPaddingTop + layout.logoAndWordmarkWrapperHeight + layout.logoAndWordmarkWrapperMarginBottom - layout.searchWrapperFixedScrollPaddingTop;
  16181    MAIN_OFFSET_PADDING = layout.searchWrapperPaddingTop + layout.searchWrapperPaddingBottom + layout.searchInnerWrapperMinHeight + layout.logoAndWordmarkWrapperHeight + layout.logoAndWordmarkWrapperMarginBottom;
  16182 
  16183    // Edge case if logo and thums are turned off, but Var A is enabled
  16184    if (SCROLL_THRESHOLD < 1) {
  16185      SCROLL_THRESHOLD = 1;
  16186    }
  16187    if (__webpack_require__.g.scrollY > SCROLL_THRESHOLD && !this.state.fixedSearch) {
  16188      this.setState({
  16189        fixedSearch: true,
  16190        fixedNavStyle: {
  16191          paddingBlockStart: `${MAIN_OFFSET_PADDING}px`
  16192        }
  16193      });
  16194    } else if (__webpack_require__.g.scrollY <= SCROLL_THRESHOLD && this.state.fixedSearch) {
  16195      this.setState({
  16196        fixedSearch: false,
  16197        fixedNavStyle: {}
  16198      });
  16199    }
  16200  }
  16201  openPreferences() {
  16202    this.props.dispatch(actionCreators.OnlyToMain({
  16203      type: actionTypes.SETTINGS_OPEN
  16204    }));
  16205    this.props.dispatch(actionCreators.UserEvent({
  16206      event: "OPEN_NEWTAB_PREFS"
  16207    }));
  16208  }
  16209  openCustomizationMenu() {
  16210    this.props.dispatch({
  16211      type: actionTypes.SHOW_PERSONALIZE
  16212    });
  16213    this.props.dispatch(actionCreators.UserEvent({
  16214      event: "SHOW_PERSONALIZE"
  16215    }));
  16216  }
  16217  closeCustomizationMenu() {
  16218    if (this.props.App.customizeMenuVisible) {
  16219      this.props.dispatch({
  16220        type: actionTypes.HIDE_PERSONALIZE
  16221      });
  16222      this.props.dispatch(actionCreators.UserEvent({
  16223        event: "HIDE_PERSONALIZE"
  16224      }));
  16225    }
  16226  }
  16227  handleOnKeyDown(e) {
  16228    if (e.key === "Escape") {
  16229      this.closeCustomizationMenu();
  16230    }
  16231  }
  16232  setPref(pref, value) {
  16233    this.props.dispatch(actionCreators.SetPref(pref, value));
  16234  }
  16235  applyBodyClasses() {
  16236    const {
  16237      body
  16238    } = this.props.document;
  16239    if (!body) {
  16240      return;
  16241    }
  16242    if (!body.classList.contains("activity-stream")) {
  16243      body.classList.add("activity-stream");
  16244    }
  16245  }
  16246  renderWallpaperAttribution() {
  16247    const {
  16248      wallpaperList
  16249    } = this.props.Wallpapers;
  16250    const activeWallpaper = this.props.Prefs.values[`newtabWallpapers.wallpaper`];
  16251    const selected = wallpaperList.find(wp => wp.title === activeWallpaper);
  16252    // make sure a wallpaper is selected and that the attribution also exists
  16253    if (!selected?.attribution) {
  16254      return null;
  16255    }
  16256    const {
  16257      name: authorDetails,
  16258      webpage
  16259    } = selected.attribution;
  16260    if (activeWallpaper && wallpaperList && authorDetails.url) {
  16261      return /*#__PURE__*/external_React_default().createElement("p", {
  16262        className: `wallpaper-attribution`,
  16263        key: authorDetails.string,
  16264        "data-l10n-id": "newtab-wallpaper-attribution",
  16265        "data-l10n-args": JSON.stringify({
  16266          author_string: authorDetails.string,
  16267          author_url: authorDetails.url,
  16268          webpage_string: webpage.string,
  16269          webpage_url: webpage.url
  16270        })
  16271      }, /*#__PURE__*/external_React_default().createElement("a", {
  16272        "data-l10n-name": "name-link",
  16273        href: authorDetails.url
  16274      }, authorDetails.string), /*#__PURE__*/external_React_default().createElement("a", {
  16275        "data-l10n-name": "webpage-link",
  16276        href: webpage.url
  16277      }, webpage.string));
  16278    }
  16279    return null;
  16280  }
  16281  async updateWallpaper() {
  16282    const prefs = this.props.Prefs.values;
  16283    const selectedWallpaper = prefs["newtabWallpapers.wallpaper"];
  16284    const {
  16285      wallpaperList,
  16286      uploadedWallpaper: uploadedWallpaperUrl
  16287    } = this.props.Wallpapers;
  16288    const uploadedWallpaperTheme = prefs["newtabWallpapers.customWallpaper.theme"];
  16289    // Uuse this.prefersDarkQuery since this.state.colorMode can be undefined when this is called
  16290    const colorMode = this.prefersDarkQuery?.matches ? "dark" : "light";
  16291    let url = "";
  16292    let color = "transparent";
  16293    let newTheme = colorMode;
  16294    let backgroundPosition = "center";
  16295 
  16296    // if no selected wallpaper fallback to browser/theme styles
  16297    if (!selectedWallpaper) {
  16298      __webpack_require__.g.document?.body.style.removeProperty("--newtab-wallpaper");
  16299      __webpack_require__.g.document?.body.style.removeProperty("--newtab-wallpaper-color");
  16300      __webpack_require__.g.document?.body.style.removeProperty("--newtab-wallpaper-backgroundPosition");
  16301      __webpack_require__.g.document?.body.classList.remove("lightWallpaper", "darkWallpaper");
  16302      return;
  16303    }
  16304 
  16305    // uploaded wallpaper
  16306    if (selectedWallpaper === "custom" && uploadedWallpaperUrl) {
  16307      url = uploadedWallpaperUrl;
  16308      color = "transparent";
  16309      // Note: There is no method to set a specific background position for custom wallpapers
  16310      backgroundPosition = "center";
  16311      newTheme = uploadedWallpaperTheme || colorMode;
  16312    } else if (wallpaperList) {
  16313      const wallpaper = wallpaperList.find(wp => wp.title === selectedWallpaper);
  16314      // solid color picker
  16315      if (selectedWallpaper.includes("solid-color-picker")) {
  16316        const regexRGB = /#([a-fA-F0-9]{6})/;
  16317        const hex = selectedWallpaper.match(regexRGB)?.[0];
  16318        url = "";
  16319        color = hex;
  16320        const rgbColors = this.getRGBColors(hex);
  16321        newTheme = this.isWallpaperColorDark(rgbColors) ? "dark" : "light";
  16322        // standard wallpaper & solid colors
  16323      } else if (selectedWallpaper) {
  16324        url = wallpaper?.wallpaperUrl || "";
  16325        backgroundPosition = wallpaper?.background_position || "center";
  16326        color = wallpaper?.solid_color || "transparent";
  16327        newTheme = wallpaper?.theme || colorMode;
  16328        // if a solid color, determine if dark or light
  16329        if (wallpaper?.solid_color) {
  16330          const rgbColors = this.getRGBColors(wallpaper.solid_color);
  16331          const isColorDark = this.isWallpaperColorDark(rgbColors);
  16332          newTheme = isColorDark ? "dark" : "light";
  16333        }
  16334      }
  16335    }
  16336    __webpack_require__.g.document?.body.style.setProperty("--newtab-wallpaper", `url(${url})`);
  16337    __webpack_require__.g.document?.body.style.setProperty("--newtab-wallpaper-backgroundPosition", backgroundPosition);
  16338    __webpack_require__.g.document?.body.style.setProperty("--newtab-wallpaper-color", color || "transparent");
  16339    __webpack_require__.g.document?.body.classList.remove("lightWallpaper", "darkWallpaper");
  16340    __webpack_require__.g.document?.body.classList.add(newTheme === "dark" ? "darkWallpaper" : "lightWallpaper");
  16341  }
  16342  shouldShowOMCHighlight(componentId) {
  16343    const messageData = this.props.Messages?.messageData;
  16344    if (!messageData || Object.keys(messageData).length === 0) {
  16345      return false;
  16346    }
  16347    return messageData?.content?.messageType === componentId;
  16348  }
  16349  toggleDownloadHighlight() {
  16350    this.setState(prevState => {
  16351      const override = !(prevState.showDownloadHighlightOverride ?? this.shouldShowOMCHighlight("DownloadMobilePromoHighlight"));
  16352      if (override) {
  16353        // Emit an open event manually since OMC isn't handling it
  16354        this.props.dispatch(actionCreators.DiscoveryStreamUserEvent({
  16355          event: "FEATURE_HIGHLIGHT_OPEN",
  16356          source: "FEATURE_HIGHLIGHT",
  16357          value: {
  16358            feature: "FEATURE_DOWNLOAD_MOBILE_PROMO"
  16359          }
  16360        }));
  16361      }
  16362      return {
  16363        showDownloadHighlightOverride: override
  16364      };
  16365    });
  16366  }
  16367  handleDismissDownloadHighlight() {
  16368    this.setState({
  16369      showDownloadHighlightOverride: false
  16370    });
  16371  }
  16372  getRGBColors(input) {
  16373    if (input.length !== 7) {
  16374      return [];
  16375    }
  16376    const r = parseInt(input.substr(1, 2), 16);
  16377    const g = parseInt(input.substr(3, 2), 16);
  16378    const b = parseInt(input.substr(5, 2), 16);
  16379    return [r, g, b];
  16380  }
  16381  isWallpaperColorDark([r, g, b]) {
  16382    return 0.2125 * r + 0.7154 * g + 0.0721 * b <= 110;
  16383  }
  16384  toggleSectionsMgmtPanel() {
  16385    this.setState(prevState => ({
  16386      showSectionsMgmtPanel: !prevState.showSectionsMgmtPanel
  16387    }));
  16388  }
  16389  shouldDisplayTopicSelectionModal() {
  16390    const prefs = this.props.Prefs.values;
  16391    const pocketEnabled = prefs["feeds.section.topstories"] && prefs["feeds.system.topstories"];
  16392    const topicSelectionOnboardingEnabled = prefs["discoverystream.topicSelection.onboarding.enabled"] && pocketEnabled;
  16393    const maybeShowModal = prefs["discoverystream.topicSelection.onboarding.maybeDisplay"];
  16394    const displayTimeout = prefs["discoverystream.topicSelection.onboarding.displayTimeout"];
  16395    const lastDisplayed = prefs["discoverystream.topicSelection.onboarding.lastDisplayed"];
  16396    const displayCount = prefs["discoverystream.topicSelection.onboarding.displayCount"];
  16397    if (!maybeShowModal || !prefs["discoverystream.topicSelection.enabled"] || !topicSelectionOnboardingEnabled) {
  16398      return;
  16399    }
  16400    const day = 24 * 60 * 60 * 1000;
  16401    const now = new Date().getTime();
  16402    const timeoutOccured = now - parseFloat(lastDisplayed) > displayTimeout;
  16403    if (displayCount < 3) {
  16404      if (displayCount === 0 || timeoutOccured) {
  16405        this.props.dispatch(actionCreators.BroadcastToContent({
  16406          type: actionTypes.TOPIC_SELECTION_SPOTLIGHT_OPEN
  16407        }));
  16408        this.setPref("discoverystream.topicSelection.onboarding.displayTimeout", day);
  16409      }
  16410    }
  16411  }
  16412 
  16413  // eslint-disable-next-line max-statements, complexity
  16414  render() {
  16415    const {
  16416      props
  16417    } = this;
  16418    const {
  16419      App,
  16420      DiscoveryStream
  16421    } = props;
  16422    const {
  16423      initialized,
  16424      customizeMenuVisible
  16425    } = App;
  16426    const prefs = props.Prefs.values;
  16427    const activeWallpaper = prefs[`newtabWallpapers.wallpaper`];
  16428    const wallpapersEnabled = prefs["newtabWallpapers.enabled"];
  16429    const weatherEnabled = prefs.showWeather;
  16430    const {
  16431      showTopicSelection
  16432    } = DiscoveryStream;
  16433    const mayShowTopicSelection = showTopicSelection && prefs["discoverystream.topicSelection.enabled"];
  16434    const isDiscoveryStream = props.DiscoveryStream.config && props.DiscoveryStream.config.enabled;
  16435    let filteredSections = props.Sections.filter(section => section.id !== "topstories");
  16436    const pocketEnabled = prefs["feeds.section.topstories"] && prefs["feeds.system.topstories"];
  16437    const noSectionsEnabled = !prefs["feeds.topsites"] && !pocketEnabled && filteredSections.filter(section => section.enabled).length === 0;
  16438    const enabledSections = {
  16439      topSitesEnabled: prefs["feeds.topsites"],
  16440      pocketEnabled: prefs["feeds.section.topstories"],
  16441      showInferredPersonalizationEnabled: prefs[Base_PREF_INFERRED_PERSONALIZATION_USER],
  16442      topSitesRowsCount: prefs.topSitesRows,
  16443      weatherEnabled: prefs.showWeather
  16444    };
  16445    const pocketRegion = prefs["feeds.system.topstories"];
  16446    const mayHaveInferredPersonalization = prefs[PREF_INFERRED_PERSONALIZATION_SYSTEM];
  16447    const mayHaveWeather = prefs["system.showWeather"] || prefs.trainhopConfig?.weather?.enabled;
  16448    const supportUrl = prefs["support.url"];
  16449 
  16450    // Weather can be enabled and not rendered in the top right corner
  16451    const shouldDisplayWeather = prefs.showWeather && this.props.weatherPlacement === "header";
  16452 
  16453    // Widgets experiment pref check
  16454    const nimbusWidgetsEnabled = prefs.widgetsConfig?.enabled;
  16455    const nimbusListsEnabled = prefs.widgetsConfig?.listsEnabled;
  16456    const nimbusTimerEnabled = prefs.widgetsConfig?.timerEnabled;
  16457    const nimbusWidgetsTrainhopEnabled = prefs.trainhopConfig?.widgets?.enabled;
  16458    const nimbusListsTrainhopEnabled = prefs.trainhopConfig?.widgets?.listsEnabled;
  16459    const nimbusTimerTrainhopEnabled = prefs.trainhopConfig?.widgets?.timerEnabled;
  16460    const mayHaveWidgets = prefs["widgets.system.enabled"] || nimbusWidgetsEnabled || nimbusWidgetsTrainhopEnabled;
  16461    const mayHaveListsWidget = prefs["widgets.system.lists.enabled"] || nimbusListsEnabled || nimbusListsTrainhopEnabled;
  16462    const mayHaveTimerWidget = prefs["widgets.system.focusTimer.enabled"] || nimbusTimerEnabled || nimbusTimerTrainhopEnabled;
  16463 
  16464    // These prefs set the initial values on the Customize panel toggle switches
  16465    const enabledWidgets = {
  16466      listsEnabled: prefs["widgets.lists.enabled"],
  16467      timerEnabled: prefs["widgets.focusTimer.enabled"],
  16468      weatherEnabled: prefs.showWeather
  16469    };
  16470 
  16471    // Mobile Download Promo Pref Checks
  16472    const mobileDownloadPromoEnabled = prefs["mobileDownloadModal.enabled"];
  16473    const mobileDownloadPromoVariantAEnabled = prefs["mobileDownloadModal.variant-a"];
  16474    const mobileDownloadPromoVariantBEnabled = prefs["mobileDownloadModal.variant-b"];
  16475    const mobileDownloadPromoVariantCEnabled = prefs["mobileDownloadModal.variant-c"];
  16476    const mobileDownloadPromoVariantABorC = mobileDownloadPromoVariantAEnabled || mobileDownloadPromoVariantBEnabled || mobileDownloadPromoVariantCEnabled;
  16477    const mobileDownloadPromoWrapperHeightModifier = prefs["weather.display"] === "detailed" && weatherEnabled && shouldDisplayWeather && mayHaveWeather ? "is-tall" : "";
  16478    const sectionsEnabled = prefs["discoverystream.sections.enabled"];
  16479    const topicLabelsEnabled = prefs["discoverystream.topicLabels.enabled"];
  16480    const sectionsCustomizeMenuPanelEnabled = prefs["discoverystream.sections.customizeMenuPanel.enabled"];
  16481    const sectionsPersonalizationEnabled = prefs["discoverystream.sections.personalization.enabled"];
  16482 
  16483    // Logic to show follow/block topic mgmt panel in Customize panel
  16484    const mayHavePersonalizedTopicSections = sectionsPersonalizationEnabled && topicLabelsEnabled && sectionsEnabled && sectionsCustomizeMenuPanelEnabled && DiscoveryStream.feeds.loaded;
  16485    const featureClassName = [mobileDownloadPromoEnabled && mobileDownloadPromoVariantABorC && "has-mobile-download-promo",
  16486    // Mobile download promo modal is enabled/visible
  16487    weatherEnabled && mayHaveWeather && shouldDisplayWeather && "has-weather",
  16488    // Weather widget is enabled/visible
  16489    prefs.showSearch ? "has-search" : "no-search",
  16490    // layoutsVariantAEnabled ? "layout-variant-a" : "", // Layout experiment variant A
  16491    // layoutsVariantBEnabled ? "layout-variant-b" : "", // Layout experiment variant B
  16492    pocketEnabled ? "has-recommended-stories" : "no-recommended-stories", sectionsEnabled ? "has-sections-grid" : ""].filter(v => v).join(" ");
  16493    const outerClassName = ["outer-wrapper", isDiscoveryStream && pocketEnabled && "ds-outer-wrapper-search-alignment", isDiscoveryStream && "ds-outer-wrapper-breakpoint-override", prefs.showSearch && this.state.fixedSearch && !noSectionsEnabled && "fixed-search", prefs.showSearch && noSectionsEnabled && "only-search", prefs["feeds.topsites"] && !pocketEnabled && !prefs.showSearch && "only-topsites", noSectionsEnabled && "no-sections", prefs["logowordmark.alwaysVisible"] && "visible-logo"].filter(v => v).join(" ");
  16494 
  16495    // If state.showDownloadHighlightOverride has value, let it override the logic
  16496    // Otherwise, defer to OMC message display logic
  16497    const shouldShowDownloadHighlight = this.state.showDownloadHighlightOverride ?? this.shouldShowOMCHighlight("DownloadMobilePromoHighlight");
  16498    return /*#__PURE__*/external_React_default().createElement("div", {
  16499      className: featureClassName
  16500    }, /*#__PURE__*/external_React_default().createElement("div", {
  16501      className: "weatherWrapper"
  16502    }, shouldDisplayWeather && /*#__PURE__*/external_React_default().createElement(ErrorBoundary, null, /*#__PURE__*/external_React_default().createElement(Weather_Weather, null))), /*#__PURE__*/external_React_default().createElement("div", {
  16503      className: `mobileDownloadPromoWrapper ${mobileDownloadPromoWrapperHeightModifier}`
  16504    }, mobileDownloadPromoEnabled && mobileDownloadPromoVariantABorC && /*#__PURE__*/external_React_default().createElement(ErrorBoundary, null, /*#__PURE__*/external_React_default().createElement(DownloadModalToggle, {
  16505      isActive: shouldShowDownloadHighlight,
  16506      onClick: this.toggleDownloadHighlight
  16507    }), shouldShowDownloadHighlight && /*#__PURE__*/external_React_default().createElement(MessageWrapper, {
  16508      hiddenOverride: shouldShowDownloadHighlight,
  16509      onDismiss: this.handleDismissDownloadHighlight,
  16510      dispatch: this.props.dispatch
  16511    }, /*#__PURE__*/external_React_default().createElement(DownloadMobilePromoHighlight, {
  16512      position: `inset-inline-start inset-block-end`,
  16513      dispatch: this.props.dispatch
  16514    })))), /*#__PURE__*/external_React_default().createElement("div", {
  16515      className: outerClassName,
  16516      onClick: this.closeCustomizationMenu
  16517    }, /*#__PURE__*/external_React_default().createElement("main", {
  16518      className: "newtab-main",
  16519      style: this.state.fixedNavStyle
  16520    }, prefs.showSearch && /*#__PURE__*/external_React_default().createElement("div", {
  16521      className: "non-collapsible-section"
  16522    }, /*#__PURE__*/external_React_default().createElement(ErrorBoundary, null, /*#__PURE__*/external_React_default().createElement(Search_Search, Base_extends({
  16523      showLogo: noSectionsEnabled || prefs["logowordmark.alwaysVisible"]
  16524    }, props.Search)))), !prefs.showSearch && !noSectionsEnabled && /*#__PURE__*/external_React_default().createElement(Logo, null), /*#__PURE__*/external_React_default().createElement("div", {
  16525      className: `body-wrapper${initialized ? " on" : ""}`
  16526    }, isDiscoveryStream ? /*#__PURE__*/external_React_default().createElement(ErrorBoundary, {
  16527      className: "borderless-error"
  16528    }, /*#__PURE__*/external_React_default().createElement(DiscoveryStreamBase, {
  16529      locale: props.App.locale,
  16530      firstVisibleTimestamp: this.state.firstVisibleTimestamp,
  16531      placeholder: this.isSpocsOnDemandExpired
  16532    })) : /*#__PURE__*/external_React_default().createElement(Sections_Sections, null)), /*#__PURE__*/external_React_default().createElement(ConfirmDialog, null), wallpapersEnabled && this.renderWallpaperAttribution()), /*#__PURE__*/external_React_default().createElement("aside", null, this.props.Notifications?.showNotifications && /*#__PURE__*/external_React_default().createElement(ErrorBoundary, null, /*#__PURE__*/external_React_default().createElement(Notifications_Notifications, {
  16533      dispatch: this.props.dispatch
  16534    }))), mayShowTopicSelection && pocketEnabled && /*#__PURE__*/external_React_default().createElement(TopicSelection, {
  16535      supportUrl: supportUrl
  16536    })), /*#__PURE__*/external_React_default().createElement("menu", {
  16537      className: "personalizeButtonWrapper"
  16538    }, /*#__PURE__*/external_React_default().createElement(CustomizeMenu, {
  16539      onClose: this.closeCustomizationMenu,
  16540      onOpen: this.openCustomizationMenu,
  16541      openPreferences: this.openPreferences,
  16542      setPref: this.setPref,
  16543      enabledSections: enabledSections,
  16544      enabledWidgets: enabledWidgets,
  16545      wallpapersEnabled: wallpapersEnabled,
  16546      activeWallpaper: activeWallpaper,
  16547      pocketRegion: pocketRegion,
  16548      mayHaveTopicSections: mayHavePersonalizedTopicSections,
  16549      mayHaveInferredPersonalization: mayHaveInferredPersonalization,
  16550      mayHaveWeather: mayHaveWeather,
  16551      mayHaveWidgets: mayHaveWidgets,
  16552      mayHaveTimerWidget: mayHaveTimerWidget,
  16553      mayHaveListsWidget: mayHaveListsWidget,
  16554      showing: customizeMenuVisible,
  16555      toggleSectionsMgmtPanel: this.toggleSectionsMgmtPanel,
  16556      showSectionsMgmtPanel: this.state.showSectionsMgmtPanel
  16557    }), this.shouldShowOMCHighlight("CustomWallpaperHighlight") && /*#__PURE__*/external_React_default().createElement(MessageWrapper, {
  16558      dispatch: this.props.dispatch
  16559    }, /*#__PURE__*/external_React_default().createElement(WallpaperFeatureHighlight, {
  16560      position: "inset-block-start inset-inline-start",
  16561      dispatch: this.props.dispatch
  16562    }))));
  16563  }
  16564 }
  16565 BaseContent.defaultProps = {
  16566  document: __webpack_require__.g.document
  16567 };
  16568 const Base = (0,external_ReactRedux_namespaceObject.connect)(state => ({
  16569  App: state.App,
  16570  Prefs: state.Prefs,
  16571  Sections: state.Sections,
  16572  DiscoveryStream: state.DiscoveryStream,
  16573  Messages: state.Messages,
  16574  Notifications: state.Notifications,
  16575  Search: state.Search,
  16576  Wallpapers: state.Wallpapers,
  16577  Weather: state.Weather,
  16578  weatherPlacement: selectWeatherPlacement(state)
  16579 }))(_Base);
  16580 ;// CONCATENATED MODULE: ./content-src/lib/detect-user-session-start.mjs
  16581 /* This Source Code Form is subject to the terms of the Mozilla Public
  16582 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  16583 * You can obtain one at http://mozilla.org/MPL/2.0/. */
  16584 
  16585 
  16586 
  16587 
  16588 const detect_user_session_start_VISIBLE = "visible";
  16589 const detect_user_session_start_VISIBILITY_CHANGE_EVENT = "visibilitychange";
  16590 
  16591 class DetectUserSessionStart {
  16592  constructor(store, options = {}) {
  16593    this._store = store;
  16594    // Overrides for testing
  16595    this.document = options.document || globalThis.document;
  16596    this._perfService = options.perfService || perfService;
  16597    this._onVisibilityChange = this._onVisibilityChange.bind(this);
  16598  }
  16599 
  16600  /**
  16601   * sendEventOrAddListener - Notify immediately if the page is already visible,
  16602   *                    or else set up a listener for when visibility changes.
  16603   *                    This is needed for accurate session tracking for telemetry,
  16604   *                    because tabs are pre-loaded.
  16605   */
  16606  sendEventOrAddListener() {
  16607    if (this.document.visibilityState === detect_user_session_start_VISIBLE) {
  16608      // If the document is already visible, to the user, send a notification
  16609      // immediately that a session has started.
  16610      this._sendEvent();
  16611    } else {
  16612      // If the document is not visible, listen for when it does become visible.
  16613      this.document.addEventListener(
  16614        detect_user_session_start_VISIBILITY_CHANGE_EVENT,
  16615        this._onVisibilityChange
  16616      );
  16617    }
  16618  }
  16619 
  16620  /**
  16621   * _sendEvent - Sends a message to the main process to indicate the current
  16622   *              tab is now visible to the user, includes the
  16623   *              visibility_event_rcvd_ts time in ms from the UNIX epoch.
  16624   */
  16625  _sendEvent() {
  16626    this._perfService.mark("visibility_event_rcvd_ts");
  16627 
  16628    try {
  16629      let visibility_event_rcvd_ts =
  16630        this._perfService.getMostRecentAbsMarkStartByName(
  16631          "visibility_event_rcvd_ts"
  16632        );
  16633 
  16634      this._store.dispatch(
  16635        actionCreators.AlsoToMain({
  16636          type: actionTypes.SAVE_SESSION_PERF_DATA,
  16637          data: {
  16638            visibility_event_rcvd_ts,
  16639            window_inner_width: window.innerWidth,
  16640            window_inner_height: window.innerHeight,
  16641          },
  16642        })
  16643      );
  16644    } catch (ex) {
  16645      // If this failed, it's likely because the `privacy.resistFingerprinting`
  16646      // pref is true.  We should at least not blow up.
  16647    }
  16648  }
  16649 
  16650  /**
  16651   * _onVisibilityChange - If the visibility has changed to visible, sends a notification
  16652   *                      and removes the event listener. This should only be called once per tab.
  16653   */
  16654  _onVisibilityChange() {
  16655    if (this.document.visibilityState === detect_user_session_start_VISIBLE) {
  16656      this._sendEvent();
  16657      this.document.removeEventListener(
  16658        detect_user_session_start_VISIBILITY_CHANGE_EVENT,
  16659        this._onVisibilityChange
  16660      );
  16661    }
  16662  }
  16663 }
  16664 
  16665 ;// CONCATENATED MODULE: external "Redux"
  16666 const external_Redux_namespaceObject = Redux;
  16667 ;// CONCATENATED MODULE: ./content-src/lib/init-store.mjs
  16668 /* This Source Code Form is subject to the terms of the Mozilla Public
  16669 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  16670 * You can obtain one at http://mozilla.org/MPL/2.0/. */
  16671 
  16672 
  16673 // We disable import checking here as redux is installed via the npm packages
  16674 // at the newtab level, rather than in the top-level package.json.
  16675 // eslint-disable-next-line import/no-unresolved
  16676 
  16677 
  16678 const MERGE_STORE_ACTION = "NEW_TAB_INITIAL_STATE";
  16679 const OUTGOING_MESSAGE_NAME = "ActivityStream:ContentToMain";
  16680 const INCOMING_MESSAGE_NAME = "ActivityStream:MainToContent";
  16681 
  16682 /**
  16683 * A higher-order function which returns a reducer that, on MERGE_STORE action,
  16684 * will return the action.data object merged into the previous state.
  16685 *
  16686 * For all other actions, it merely calls mainReducer.
  16687 *
  16688 * Because we want this to merge the entire state object, it's written as a
  16689 * higher order function which takes the main reducer (itself often a call to
  16690 * combineReducers) as a parameter.
  16691 *
  16692 * @param  {function} mainReducer reducer to call if action != MERGE_STORE_ACTION
  16693 * @return {function}             a reducer that, on MERGE_STORE_ACTION action,
  16694 *                                will return the action.data object merged
  16695 *                                into the previous state, and the result
  16696 *                                of calling mainReducer otherwise.
  16697 */
  16698 function mergeStateReducer(mainReducer) {
  16699  return (prevState, action) => {
  16700    if (action.type === MERGE_STORE_ACTION) {
  16701      return { ...prevState, ...action.data };
  16702    }
  16703 
  16704    return mainReducer(prevState, action);
  16705  };
  16706 }
  16707 
  16708 /**
  16709 * messageMiddleware - Middleware that looks for SentToMain type actions, and sends them if necessary
  16710 */
  16711 const messageMiddleware = () => next => action => {
  16712  const skipLocal = action.meta && action.meta.skipLocal;
  16713  if (actionUtils.isSendToMain(action)) {
  16714    RPMSendAsyncMessage(OUTGOING_MESSAGE_NAME, action);
  16715  }
  16716  if (!skipLocal) {
  16717    next(action);
  16718  }
  16719 };
  16720 
  16721 const rehydrationMiddleware = ({ getState }) => {
  16722  // NB: The parameter here is MiddlewareAPI which looks like a Store and shares
  16723  // the same getState, so attached properties are accessible from the store.
  16724  getState.didRehydrate = false;
  16725  getState.didRequestInitialState = false;
  16726  return next => action => {
  16727    if (getState.didRehydrate || window.__FROM_STARTUP_CACHE__) {
  16728      // Startup messages can be safely ignored by the about:home document
  16729      // stored in the startup cache.
  16730      if (
  16731        window.__FROM_STARTUP_CACHE__ &&
  16732        action.meta &&
  16733        action.meta.isStartup
  16734      ) {
  16735        return null;
  16736      }
  16737      return next(action);
  16738    }
  16739 
  16740    const isMergeStoreAction = action.type === MERGE_STORE_ACTION;
  16741    const isRehydrationRequest = action.type === actionTypes.NEW_TAB_STATE_REQUEST;
  16742 
  16743    if (isRehydrationRequest) {
  16744      getState.didRequestInitialState = true;
  16745      return next(action);
  16746    }
  16747 
  16748    if (isMergeStoreAction) {
  16749      getState.didRehydrate = true;
  16750      return next(action);
  16751    }
  16752 
  16753    // If init happened after our request was made, we need to re-request
  16754    if (getState.didRequestInitialState && action.type === actionTypes.INIT) {
  16755      return next(actionCreators.AlsoToMain({ type: actionTypes.NEW_TAB_STATE_REQUEST }));
  16756    }
  16757 
  16758    if (
  16759      actionUtils.isBroadcastToContent(action) ||
  16760      actionUtils.isSendToOneContent(action) ||
  16761      actionUtils.isSendToPreloaded(action)
  16762    ) {
  16763      // Note that actions received before didRehydrate will not be dispatched
  16764      // because this could negatively affect preloading and the the state
  16765      // will be replaced by rehydration anyway.
  16766      return null;
  16767    }
  16768 
  16769    return next(action);
  16770  };
  16771 };
  16772 
  16773 /**
  16774 * initStore - Create a store and listen for incoming actions
  16775 *
  16776 * @param  {object} reducers An object containing Redux reducers
  16777 * @param  {object} intialState (optional) The initial state of the store, if desired
  16778 * @return {object}          A redux store
  16779 */
  16780 function initStore(reducers, initialState) {
  16781  const store = (0,external_Redux_namespaceObject.createStore)(
  16782    mergeStateReducer((0,external_Redux_namespaceObject.combineReducers)(reducers)),
  16783    initialState,
  16784    globalThis.RPMAddMessageListener &&
  16785      (0,external_Redux_namespaceObject.applyMiddleware)(rehydrationMiddleware, messageMiddleware)
  16786  );
  16787 
  16788  if (globalThis.RPMAddMessageListener) {
  16789    globalThis.RPMAddMessageListener(INCOMING_MESSAGE_NAME, msg => {
  16790      try {
  16791        store.dispatch(msg.data);
  16792      } catch (ex) {
  16793        console.error("Content msg:", msg, "Dispatch error: ", ex);
  16794        dump(
  16795          `Content msg: ${JSON.stringify(msg)}\nDispatch error: ${ex}\n${
  16796            ex.stack
  16797          }`
  16798        );
  16799      }
  16800    });
  16801  }
  16802 
  16803  return store;
  16804 }
  16805 
  16806 ;// CONCATENATED MODULE: external "ReactDOM"
  16807 const external_ReactDOM_namespaceObject = ReactDOM;
  16808 var external_ReactDOM_default = /*#__PURE__*/__webpack_require__.n(external_ReactDOM_namespaceObject);
  16809 ;// CONCATENATED MODULE: ./content-src/activity-stream.jsx
  16810 /* This Source Code Form is subject to the terms of the Mozilla Public
  16811 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  16812 * You can obtain one at http://mozilla.org/MPL/2.0/. */
  16813 
  16814 
  16815 
  16816 
  16817 
  16818 
  16819 
  16820 
  16821 
  16822 const NewTab = ({
  16823  store
  16824 }) => /*#__PURE__*/external_React_default().createElement(external_ReactRedux_namespaceObject.Provider, {
  16825  store: store
  16826 }, /*#__PURE__*/external_React_default().createElement(Base, null));
  16827 function doRequestWhenReady() {
  16828  // If this document has already gone into the background by the time we've reached
  16829  // here, we can deprioritize the request until the event loop
  16830  // frees up. If, however, the visibility changes, we then send the request.
  16831  const doRequestPromise = new Promise(resolve => {
  16832    let didRequest = false;
  16833    let requestIdleCallbackId = 0;
  16834    function doRequest() {
  16835      if (!didRequest) {
  16836        if (requestIdleCallbackId) {
  16837          cancelIdleCallback(requestIdleCallbackId);
  16838        }
  16839        didRequest = true;
  16840        resolve();
  16841      }
  16842    }
  16843    if (document.hidden) {
  16844      requestIdleCallbackId = requestIdleCallback(doRequest);
  16845      addEventListener("visibilitychange", doRequest, {
  16846        once: true
  16847      });
  16848    } else {
  16849      resolve();
  16850    }
  16851  });
  16852  return doRequestPromise;
  16853 }
  16854 function renderWithoutState() {
  16855  const store = initStore(reducers);
  16856  new DetectUserSessionStart(store).sendEventOrAddListener();
  16857  doRequestWhenReady().then(() => {
  16858    // If state events happened before we got here, we can request state again.
  16859    store.dispatch(actionCreators.AlsoToMain({
  16860      type: actionTypes.NEW_TAB_STATE_REQUEST
  16861    }));
  16862    // If we rendered without state, we don't need the startup cache.
  16863    store.dispatch(actionCreators.OnlyToMain({
  16864      type: actionTypes.NEW_TAB_STATE_REQUEST_WITHOUT_STARTUPCACHE
  16865    }));
  16866  });
  16867  external_ReactDOM_default().hydrate(/*#__PURE__*/external_React_default().createElement(NewTab, {
  16868    store: store
  16869  }), document.getElementById("root"));
  16870 }
  16871 function renderCache(initialState) {
  16872  if (initialState) {
  16873    initialState.App.isForStartupCache.App = false;
  16874  }
  16875  const store = initStore(reducers, initialState);
  16876  new DetectUserSessionStart(store).sendEventOrAddListener();
  16877  doRequestWhenReady().then(() => {
  16878    // If state events happened before we got here,
  16879    // we can notify main that we need updates.
  16880    // The individual feeds know what state is not cached.
  16881    store.dispatch(actionCreators.OnlyToMain({
  16882      type: actionTypes.NEW_TAB_STATE_REQUEST_STARTUPCACHE
  16883    }));
  16884  });
  16885  external_ReactDOM_default().hydrate(/*#__PURE__*/external_React_default().createElement(NewTab, {
  16886    store: store
  16887  }), document.getElementById("root"));
  16888 }
  16889 NewtabRenderUtils = __webpack_exports__;
  16890 /******/ })()
  16891 ;