Actions.jsm 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. "use strict";
  5. this.MAIN_MESSAGE_TYPE = "ActivityStream:Main";
  6. this.CONTENT_MESSAGE_TYPE = "ActivityStream:Content";
  7. this.PRELOAD_MESSAGE_TYPE = "ActivityStream:PreloadedBrowser";
  8. this.UI_CODE = 1;
  9. this.BACKGROUND_PROCESS = 2;
  10. /**
  11. * globalImportContext - Are we in UI code (i.e. react, a dom) or some kind of background process?
  12. * Use this in action creators if you need different logic
  13. * for ui/background processes.
  14. */
  15. const globalImportContext = typeof Window === "undefined" ? BACKGROUND_PROCESS : UI_CODE;
  16. // Export for tests
  17. this.globalImportContext = globalImportContext;
  18. // Create an object that avoids accidental differing key/value pairs:
  19. // {
  20. // INIT: "INIT",
  21. // UNINIT: "UNINIT"
  22. // }
  23. const actionTypes = {};
  24. for (const type of [
  25. "ADDONS_INFO_REQUEST",
  26. "ADDONS_INFO_RESPONSE",
  27. "ARCHIVE_FROM_POCKET",
  28. "AS_ROUTER_INITIALIZED",
  29. "AS_ROUTER_PREF_CHANGED",
  30. "AS_ROUTER_TARGETING_UPDATE",
  31. "AS_ROUTER_TELEMETRY_USER_EVENT",
  32. "BLOCK_URL",
  33. "BOOKMARK_URL",
  34. "COPY_DOWNLOAD_LINK",
  35. "DELETE_BOOKMARK_BY_ID",
  36. "DELETE_FROM_POCKET",
  37. "DELETE_HISTORY_URL",
  38. "DIALOG_CANCEL",
  39. "DIALOG_OPEN",
  40. "DISCOVERY_STREAM_CONFIG_CHANGE",
  41. "DISCOVERY_STREAM_CONFIG_SETUP",
  42. "DISCOVERY_STREAM_CONFIG_SET_VALUE",
  43. "DISCOVERY_STREAM_FEEDS_UPDATE",
  44. "DISCOVERY_STREAM_FEED_UPDATE",
  45. "DISCOVERY_STREAM_IMPRESSION_STATS",
  46. "DISCOVERY_STREAM_LAYOUT_RESET",
  47. "DISCOVERY_STREAM_LAYOUT_UPDATE",
  48. "DISCOVERY_STREAM_LINK_BLOCKED",
  49. "DISCOVERY_STREAM_LOADED_CONTENT",
  50. "DISCOVERY_STREAM_RETRY_FEED",
  51. "DISCOVERY_STREAM_SPOCS_CAPS",
  52. "DISCOVERY_STREAM_SPOCS_ENDPOINT",
  53. "DISCOVERY_STREAM_SPOCS_FILL",
  54. "DISCOVERY_STREAM_SPOCS_UPDATE",
  55. "DISCOVERY_STREAM_SPOC_IMPRESSION",
  56. "DOWNLOAD_CHANGED",
  57. "FAKE_FOCUS_SEARCH",
  58. "FILL_SEARCH_TERM",
  59. "HANDOFF_SEARCH_TO_AWESOMEBAR",
  60. "HIDE_SEARCH",
  61. "INIT",
  62. "NEW_TAB_INIT",
  63. "NEW_TAB_INITIAL_STATE",
  64. "NEW_TAB_LOAD",
  65. "NEW_TAB_REHYDRATED",
  66. "NEW_TAB_STATE_REQUEST",
  67. "NEW_TAB_UNLOAD",
  68. "OPEN_DOWNLOAD_FILE",
  69. "OPEN_LINK",
  70. "OPEN_NEW_WINDOW",
  71. "OPEN_PRIVATE_WINDOW",
  72. "OPEN_WEBEXT_SETTINGS",
  73. "PLACES_BOOKMARK_ADDED",
  74. "PLACES_BOOKMARK_REMOVED",
  75. "PLACES_HISTORY_CLEARED",
  76. "PLACES_LINKS_CHANGED",
  77. "PLACES_LINK_BLOCKED",
  78. "PLACES_LINK_DELETED",
  79. "PLACES_SAVED_TO_POCKET",
  80. "POCKET_CTA",
  81. "POCKET_LINK_DELETED_OR_ARCHIVED",
  82. "POCKET_LOGGED_IN",
  83. "POCKET_WAITING_FOR_SPOC",
  84. "PREFS_INITIAL_VALUES",
  85. "PREF_CHANGED",
  86. "PREVIEW_REQUEST",
  87. "PREVIEW_REQUEST_CANCEL",
  88. "PREVIEW_RESPONSE",
  89. "REMOVE_DOWNLOAD_FILE",
  90. "RICH_ICON_MISSING",
  91. "SAVE_SESSION_PERF_DATA",
  92. "SAVE_TO_POCKET",
  93. "SCREENSHOT_UPDATED",
  94. "SECTION_DEREGISTER",
  95. "SECTION_DISABLE",
  96. "SECTION_ENABLE",
  97. "SECTION_MOVE",
  98. "SECTION_OPTIONS_CHANGED",
  99. "SECTION_REGISTER",
  100. "SECTION_UPDATE",
  101. "SECTION_UPDATE_CARD",
  102. "SETTINGS_CLOSE",
  103. "SETTINGS_OPEN",
  104. "SET_PREF",
  105. "SHOW_DOWNLOAD_FILE",
  106. "SHOW_FIREFOX_ACCOUNTS",
  107. "SHOW_SEARCH",
  108. "SKIPPED_SIGNIN",
  109. "SNIPPETS_BLOCKLIST_CLEARED",
  110. "SNIPPETS_BLOCKLIST_UPDATED",
  111. "SNIPPETS_DATA",
  112. "SNIPPETS_PREVIEW_MODE",
  113. "SNIPPETS_RESET",
  114. "SNIPPET_BLOCKED",
  115. "SUBMIT_EMAIL",
  116. "SYSTEM_TICK",
  117. "TELEMETRY_IMPRESSION_STATS",
  118. "TELEMETRY_PERFORMANCE_EVENT",
  119. "TELEMETRY_UNDESIRED_EVENT",
  120. "TELEMETRY_USER_EVENT",
  121. "TOP_SITES_CANCEL_EDIT",
  122. "TOP_SITES_CLOSE_SEARCH_SHORTCUTS_MODAL",
  123. "TOP_SITES_EDIT",
  124. "TOP_SITES_INSERT",
  125. "TOP_SITES_OPEN_SEARCH_SHORTCUTS_MODAL",
  126. "TOP_SITES_PIN",
  127. "TOP_SITES_PREFS_UPDATED",
  128. "TOP_SITES_UNPIN",
  129. "TOP_SITES_UPDATED",
  130. "TOTAL_BOOKMARKS_REQUEST",
  131. "TOTAL_BOOKMARKS_RESPONSE",
  132. "TRAILHEAD_ENROLL_EVENT",
  133. "UNINIT",
  134. "UPDATE_PINNED_SEARCH_SHORTCUTS",
  135. "UPDATE_SEARCH_SHORTCUTS",
  136. "UPDATE_SECTION_PREFS",
  137. "WEBEXT_CLICK",
  138. "WEBEXT_DISMISS",
  139. ]) {
  140. actionTypes[type] = type;
  141. }
  142. // These are acceptable actions for AS Router messages to have. They can show up
  143. // as call-to-action buttons in snippets, onboarding tour, etc.
  144. const ASRouterActions = {};
  145. for (const type of [
  146. "INSTALL_ADDON_FROM_URL",
  147. "OPEN_APPLICATIONS_MENU",
  148. "OPEN_PRIVATE_BROWSER_WINDOW",
  149. "OPEN_URL",
  150. "OPEN_ABOUT_PAGE",
  151. "OPEN_PREFERENCES_PAGE",
  152. "SHOW_FIREFOX_ACCOUNTS",
  153. "PIN_CURRENT_TAB",
  154. ]) {
  155. ASRouterActions[type] = type;
  156. }
  157. // Helper function for creating routed actions between content and main
  158. // Not intended to be used by consumers
  159. function _RouteMessage(action, options) {
  160. const meta = action.meta ? {...action.meta} : {};
  161. if (!options || !options.from || !options.to) {
  162. throw new Error("Routed Messages must have options as the second parameter, and must at least include a .from and .to property.");
  163. }
  164. // For each of these fields, if they are passed as an option,
  165. // add them to the action. If they are not defined, remove them.
  166. ["from", "to", "toTarget", "fromTarget", "skipMain", "skipLocal"].forEach(o => {
  167. if (typeof options[o] !== "undefined") {
  168. meta[o] = options[o];
  169. } else if (meta[o]) {
  170. delete meta[o];
  171. }
  172. });
  173. return {...action, meta};
  174. }
  175. /**
  176. * AlsoToMain - Creates a message that will be dispatched locally and also sent to the Main process.
  177. *
  178. * @param {object} action Any redux action (required)
  179. * @param {object} options
  180. * @param {bool} skipLocal Used by OnlyToMain to skip the main reducer
  181. * @param {string} fromTarget The id of the content port from which the action originated. (optional)
  182. * @return {object} An action with added .meta properties
  183. */
  184. function AlsoToMain(action, fromTarget, skipLocal) {
  185. return _RouteMessage(action, {
  186. from: CONTENT_MESSAGE_TYPE,
  187. to: MAIN_MESSAGE_TYPE,
  188. fromTarget,
  189. skipLocal,
  190. });
  191. }
  192. /**
  193. * OnlyToMain - Creates a message that will be sent to the Main process and skip the local reducer.
  194. *
  195. * @param {object} action Any redux action (required)
  196. * @param {object} options
  197. * @param {string} fromTarget The id of the content port from which the action originated. (optional)
  198. * @return {object} An action with added .meta properties
  199. */
  200. function OnlyToMain(action, fromTarget) {
  201. return AlsoToMain(action, fromTarget, true);
  202. }
  203. /**
  204. * BroadcastToContent - Creates a message that will be dispatched to main and sent to ALL content processes.
  205. *
  206. * @param {object} action Any redux action (required)
  207. * @return {object} An action with added .meta properties
  208. */
  209. function BroadcastToContent(action) {
  210. return _RouteMessage(action, {
  211. from: MAIN_MESSAGE_TYPE,
  212. to: CONTENT_MESSAGE_TYPE,
  213. });
  214. }
  215. /**
  216. * AlsoToOneContent - Creates a message that will be will be dispatched to the main store
  217. * and also sent to a particular Content process.
  218. *
  219. * @param {object} action Any redux action (required)
  220. * @param {string} target The id of a content port
  221. * @param {bool} skipMain Used by OnlyToOneContent to skip the main process
  222. * @return {object} An action with added .meta properties
  223. */
  224. function AlsoToOneContent(action, target, skipMain) {
  225. if (!target) {
  226. throw new Error("You must provide a target ID as the second parameter of AlsoToOneContent. If you want to send to all content processes, use BroadcastToContent");
  227. }
  228. return _RouteMessage(action, {
  229. from: MAIN_MESSAGE_TYPE,
  230. to: CONTENT_MESSAGE_TYPE,
  231. toTarget: target,
  232. skipMain,
  233. });
  234. }
  235. /**
  236. * OnlyToOneContent - Creates a message that will be sent to a particular Content process
  237. * and skip the main reducer.
  238. *
  239. * @param {object} action Any redux action (required)
  240. * @param {string} target The id of a content port
  241. * @return {object} An action with added .meta properties
  242. */
  243. function OnlyToOneContent(action, target) {
  244. return AlsoToOneContent(action, target, true);
  245. }
  246. /**
  247. * AlsoToPreloaded - Creates a message that dispatched to the main reducer and also sent to the preloaded tab.
  248. *
  249. * @param {object} action Any redux action (required)
  250. * @return {object} An action with added .meta properties
  251. */
  252. function AlsoToPreloaded(action) {
  253. return _RouteMessage(action, {
  254. from: MAIN_MESSAGE_TYPE,
  255. to: PRELOAD_MESSAGE_TYPE,
  256. });
  257. }
  258. /**
  259. * UserEvent - A telemetry ping indicating a user action. This should only
  260. * be sent from the UI during a user session.
  261. *
  262. * @param {object} data Fields to include in the ping (source, etc.)
  263. * @return {object} An AlsoToMain action
  264. */
  265. function UserEvent(data) {
  266. return AlsoToMain({
  267. type: actionTypes.TELEMETRY_USER_EVENT,
  268. data,
  269. });
  270. }
  271. /**
  272. * ASRouterUserEvent - A telemetry ping indicating a user action from AS router. This should only
  273. * be sent from the UI during a user session.
  274. *
  275. * @param {object} data Fields to include in the ping (source, etc.)
  276. * @return {object} An AlsoToMain action
  277. */
  278. function ASRouterUserEvent(data) {
  279. return AlsoToMain({
  280. type: actionTypes.AS_ROUTER_TELEMETRY_USER_EVENT,
  281. data,
  282. });
  283. }
  284. /**
  285. * DiscoveryStreamSpocsFill - A telemetry ping indicating a SPOCS Fill event.
  286. *
  287. * @param {object} data Fields to include in the ping (spoc_fills, etc.)
  288. * @param {int} importContext (For testing) Override the import context for testing.
  289. * @return {object} An AlsoToMain action
  290. */
  291. function DiscoveryStreamSpocsFill(data, importContext = globalImportContext) {
  292. const action = {
  293. type: actionTypes.DISCOVERY_STREAM_SPOCS_FILL,
  294. data,
  295. };
  296. return importContext === UI_CODE ? AlsoToMain(action) : action;
  297. }
  298. /**
  299. * UndesiredEvent - A telemetry ping indicating an undesired state.
  300. *
  301. * @param {object} data Fields to include in the ping (value, etc.)
  302. * @param {int} importContext (For testing) Override the import context for testing.
  303. * @return {object} An action. For UI code, a AlsoToMain action.
  304. */
  305. function UndesiredEvent(data, importContext = globalImportContext) {
  306. const action = {
  307. type: actionTypes.TELEMETRY_UNDESIRED_EVENT,
  308. data,
  309. };
  310. return importContext === UI_CODE ? AlsoToMain(action) : action;
  311. }
  312. /**
  313. * PerfEvent - A telemetry ping indicating a performance-related event.
  314. *
  315. * @param {object} data Fields to include in the ping (value, etc.)
  316. * @param {int} importContext (For testing) Override the import context for testing.
  317. * @return {object} An action. For UI code, a AlsoToMain action.
  318. */
  319. function PerfEvent(data, importContext = globalImportContext) {
  320. const action = {
  321. type: actionTypes.TELEMETRY_PERFORMANCE_EVENT,
  322. data,
  323. };
  324. return importContext === UI_CODE ? AlsoToMain(action) : action;
  325. }
  326. /**
  327. * ImpressionStats - A telemetry ping indicating an impression stats.
  328. *
  329. * @param {object} data Fields to include in the ping
  330. * @param {int} importContext (For testing) Override the import context for testing.
  331. * #return {object} An action. For UI code, a AlsoToMain action.
  332. */
  333. function ImpressionStats(data, importContext = globalImportContext) {
  334. const action = {
  335. type: actionTypes.TELEMETRY_IMPRESSION_STATS,
  336. data,
  337. };
  338. return importContext === UI_CODE ? AlsoToMain(action) : action;
  339. }
  340. /**
  341. * DiscoveryStreamImpressionStats - A telemetry ping indicating an impression stats in Discovery Stream.
  342. *
  343. * @param {object} data Fields to include in the ping
  344. * @param {int} importContext (For testing) Override the import context for testing.
  345. * #return {object} An action. For UI code, a AlsoToMain action.
  346. */
  347. function DiscoveryStreamImpressionStats(data, importContext = globalImportContext) {
  348. const action = {
  349. type: actionTypes.DISCOVERY_STREAM_IMPRESSION_STATS,
  350. data,
  351. };
  352. return importContext === UI_CODE ? AlsoToMain(action) : action;
  353. }
  354. /**
  355. * DiscoveryStreamLoadedContent - A telemetry ping indicating a content gets loaded in Discovery Stream.
  356. *
  357. * @param {object} data Fields to include in the ping
  358. * @param {int} importContext (For testing) Override the import context for testing.
  359. * #return {object} An action. For UI code, a AlsoToMain action.
  360. */
  361. function DiscoveryStreamLoadedContent(data, importContext = globalImportContext) {
  362. const action = {
  363. type: actionTypes.DISCOVERY_STREAM_LOADED_CONTENT,
  364. data,
  365. };
  366. return importContext === UI_CODE ? AlsoToMain(action) : action;
  367. }
  368. function SetPref(name, value, importContext = globalImportContext) {
  369. const action = {type: actionTypes.SET_PREF, data: {name, value}};
  370. return importContext === UI_CODE ? AlsoToMain(action) : action;
  371. }
  372. function WebExtEvent(type, data, importContext = globalImportContext) {
  373. if (!data || !data.source) {
  374. throw new Error("WebExtEvent actions should include a property \"source\", the id of the webextension that should receive the event.");
  375. }
  376. const action = {type, data};
  377. return importContext === UI_CODE ? AlsoToMain(action) : action;
  378. }
  379. this.actionTypes = actionTypes;
  380. this.ASRouterActions = ASRouterActions;
  381. this.actionCreators = {
  382. BroadcastToContent,
  383. UserEvent,
  384. ASRouterUserEvent,
  385. UndesiredEvent,
  386. PerfEvent,
  387. ImpressionStats,
  388. AlsoToOneContent,
  389. OnlyToOneContent,
  390. AlsoToMain,
  391. OnlyToMain,
  392. AlsoToPreloaded,
  393. SetPref,
  394. WebExtEvent,
  395. DiscoveryStreamImpressionStats,
  396. DiscoveryStreamLoadedContent,
  397. DiscoveryStreamSpocsFill,
  398. };
  399. // These are helpers to test for certain kinds of actions
  400. this.actionUtils = {
  401. isSendToMain(action) {
  402. if (!action.meta) {
  403. return false;
  404. }
  405. return action.meta.to === MAIN_MESSAGE_TYPE && action.meta.from === CONTENT_MESSAGE_TYPE;
  406. },
  407. isBroadcastToContent(action) {
  408. if (!action.meta) {
  409. return false;
  410. }
  411. if (action.meta.to === CONTENT_MESSAGE_TYPE && !action.meta.toTarget) {
  412. return true;
  413. }
  414. return false;
  415. },
  416. isSendToOneContent(action) {
  417. if (!action.meta) {
  418. return false;
  419. }
  420. if (action.meta.to === CONTENT_MESSAGE_TYPE && action.meta.toTarget) {
  421. return true;
  422. }
  423. return false;
  424. },
  425. isSendToPreloaded(action) {
  426. if (!action.meta) {
  427. return false;
  428. }
  429. return action.meta.to === PRELOAD_MESSAGE_TYPE &&
  430. action.meta.from === MAIN_MESSAGE_TYPE;
  431. },
  432. isFromMain(action) {
  433. if (!action.meta) {
  434. return false;
  435. }
  436. return action.meta.from === MAIN_MESSAGE_TYPE &&
  437. action.meta.to === CONTENT_MESSAGE_TYPE;
  438. },
  439. getPortIdOfSender(action) {
  440. return (action.meta && action.meta.fromTarget) || null;
  441. },
  442. _RouteMessage,
  443. };
  444. const EXPORTED_SYMBOLS = [
  445. "actionTypes",
  446. "actionCreators",
  447. "actionUtils",
  448. "ASRouterActions",
  449. "globalImportContext",
  450. "UI_CODE",
  451. "BACKGROUND_PROCESS",
  452. "MAIN_MESSAGE_TYPE",
  453. "CONTENT_MESSAGE_TYPE",
  454. "PRELOAD_MESSAGE_TYPE",
  455. ];