Manager.mjs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. /**
  2. * @author n1474335 [n1474335@gmail.com]
  3. * @copyright Crown Copyright 2016
  4. * @license Apache-2.0
  5. */
  6. import WorkerWaiter from "./WorkerWaiter";
  7. import WindowWaiter from "./WindowWaiter";
  8. import ControlsWaiter from "./ControlsWaiter";
  9. import RecipeWaiter from "./RecipeWaiter";
  10. import OperationsWaiter from "./OperationsWaiter";
  11. import InputWaiter from "./InputWaiter";
  12. import OutputWaiter from "./OutputWaiter";
  13. import OptionsWaiter from "./OptionsWaiter";
  14. import HighlighterWaiter from "./HighlighterWaiter";
  15. import SeasonalWaiter from "./SeasonalWaiter";
  16. import BindingsWaiter from "./BindingsWaiter";
  17. import BackgroundWorkerWaiter from "./BackgroundWorkerWaiter";
  18. /**
  19. * This object controls the Waiters responsible for handling events from all areas of the app.
  20. */
  21. class Manager {
  22. /**
  23. * Manager constructor.
  24. *
  25. * @param {App} app - The main view object for CyberChef.
  26. */
  27. constructor(app) {
  28. this.app = app;
  29. // Define custom events
  30. /**
  31. * @event Manager#appstart
  32. */
  33. this.appstart = new CustomEvent("appstart", {bubbles: true});
  34. /**
  35. * @event Manager#apploaded
  36. */
  37. this.apploaded = new CustomEvent("apploaded", {bubbles: true});
  38. /**
  39. * @event Manager#operationadd
  40. */
  41. this.operationadd = new CustomEvent("operationadd", {bubbles: true});
  42. /**
  43. * @event Manager#operationremove
  44. */
  45. this.operationremove = new CustomEvent("operationremove", {bubbles: true});
  46. /**
  47. * @event Manager#oplistcreate
  48. */
  49. this.oplistcreate = new CustomEvent("oplistcreate", {bubbles: true});
  50. /**
  51. * @event Manager#statechange
  52. */
  53. this.statechange = new CustomEvent("statechange", {bubbles: true});
  54. // Define Waiter objects to handle various areas
  55. this.worker = new WorkerWaiter(this.app, this);
  56. this.window = new WindowWaiter(this.app);
  57. this.controls = new ControlsWaiter(this.app, this);
  58. this.recipe = new RecipeWaiter(this.app, this);
  59. this.ops = new OperationsWaiter(this.app, this);
  60. this.input = new InputWaiter(this.app, this);
  61. this.output = new OutputWaiter(this.app, this);
  62. this.options = new OptionsWaiter(this.app, this);
  63. this.highlighter = new HighlighterWaiter(this.app, this);
  64. this.seasonal = new SeasonalWaiter(this.app, this);
  65. this.bindings = new BindingsWaiter(this.app, this);
  66. this.background = new BackgroundWorkerWaiter(this.app, this);
  67. // Object to store dynamic handlers to fire on elements that may not exist yet
  68. this.dynamicHandlers = {};
  69. this.initialiseEventListeners();
  70. }
  71. /**
  72. * Sets up the various components and listeners.
  73. */
  74. setup() {
  75. this.worker.registerChefWorker();
  76. this.recipe.initialiseOperationDragNDrop();
  77. this.controls.initComponents();
  78. this.controls.autoBakeChange();
  79. this.bindings.updateKeybList();
  80. this.background.registerChefWorker();
  81. this.seasonal.load();
  82. }
  83. /**
  84. * Main function to handle the creation of the event listeners.
  85. */
  86. initialiseEventListeners() {
  87. // Global
  88. window.addEventListener("resize", this.window.windowResize.bind(this.window));
  89. window.addEventListener("blur", this.window.windowBlur.bind(this.window));
  90. window.addEventListener("focus", this.window.windowFocus.bind(this.window));
  91. window.addEventListener("statechange", this.app.stateChange.bind(this.app));
  92. window.addEventListener("popstate", this.app.popState.bind(this.app));
  93. // Controls
  94. document.getElementById("bake").addEventListener("click", this.controls.bakeClick.bind(this.controls));
  95. document.getElementById("auto-bake").addEventListener("change", this.controls.autoBakeChange.bind(this.controls));
  96. document.getElementById("step").addEventListener("click", this.controls.stepClick.bind(this.controls));
  97. document.getElementById("clr-recipe").addEventListener("click", this.controls.clearRecipeClick.bind(this.controls));
  98. document.getElementById("save").addEventListener("click", this.controls.saveClick.bind(this.controls));
  99. document.getElementById("save-button").addEventListener("click", this.controls.saveButtonClick.bind(this.controls));
  100. document.getElementById("save-link-recipe-checkbox").addEventListener("change", this.controls.slrCheckChange.bind(this.controls));
  101. document.getElementById("save-link-input-checkbox").addEventListener("change", this.controls.sliCheckChange.bind(this.controls));
  102. document.getElementById("load").addEventListener("click", this.controls.loadClick.bind(this.controls));
  103. document.getElementById("load-delete-button").addEventListener("click", this.controls.loadDeleteClick.bind(this.controls));
  104. document.getElementById("load-name").addEventListener("change", this.controls.loadNameChange.bind(this.controls));
  105. document.getElementById("load-button").addEventListener("click", this.controls.loadButtonClick.bind(this.controls));
  106. document.getElementById("support").addEventListener("click", this.controls.supportButtonClick.bind(this.controls));
  107. this.addMultiEventListeners("#save-texts textarea", "keyup paste", this.controls.saveTextChange, this.controls);
  108. // Operations
  109. this.addMultiEventListener("#search", "keyup paste search", this.ops.searchOperations, this.ops);
  110. this.addDynamicListener(".op-list li.operation", "dblclick", this.ops.operationDblclick, this.ops);
  111. document.getElementById("edit-favourites").addEventListener("click", this.ops.editFavouritesClick.bind(this.ops));
  112. document.getElementById("save-favourites").addEventListener("click", this.ops.saveFavouritesClick.bind(this.ops));
  113. document.getElementById("reset-favourites").addEventListener("click", this.ops.resetFavouritesClick.bind(this.ops));
  114. this.addDynamicListener(".op-list", "oplistcreate", this.ops.opListCreate, this.ops);
  115. this.addDynamicListener("li.operation", "operationadd", this.recipe.opAdd, this.recipe);
  116. // Recipe
  117. this.addDynamicListener(".arg:not(select)", "input", this.recipe.ingChange, this.recipe);
  118. this.addDynamicListener(".arg[type=checkbox], .arg[type=radio], select.arg", "change", this.recipe.ingChange, this.recipe);
  119. this.addDynamicListener(".disable-icon", "click", this.recipe.disableClick, this.recipe);
  120. this.addDynamicListener(".breakpoint", "click", this.recipe.breakpointClick, this.recipe);
  121. this.addDynamicListener("#rec-list li.operation", "dblclick", this.recipe.operationDblclick, this.recipe);
  122. this.addDynamicListener("#rec-list li.operation > div", "dblclick", this.recipe.operationChildDblclick, this.recipe);
  123. this.addDynamicListener("#rec-list .dropdown-menu.toggle-dropdown a", "click", this.recipe.dropdownToggleClick, this.recipe);
  124. this.addDynamicListener("#rec-list", "operationremove", this.recipe.opRemove.bind(this.recipe));
  125. // Input
  126. this.addMultiEventListener("#input-text", "keyup", this.input.inputChange, this.input);
  127. this.addMultiEventListener("#input-text", "paste", this.input.inputPaste, this.input);
  128. document.getElementById("reset-layout").addEventListener("click", this.app.resetLayout.bind(this.app));
  129. document.getElementById("clr-io").addEventListener("click", this.input.clearIoClick.bind(this.input));
  130. this.addListeners("#input-text,#input-file", "dragover", this.input.inputDragover, this.input);
  131. this.addListeners("#input-text,#input-file", "dragleave", this.input.inputDragleave, this.input);
  132. this.addListeners("#input-text,#input-file", "drop", this.input.inputDrop, this.input);
  133. document.getElementById("input-text").addEventListener("scroll", this.highlighter.inputScroll.bind(this.highlighter));
  134. document.getElementById("input-text").addEventListener("mouseup", this.highlighter.inputMouseup.bind(this.highlighter));
  135. document.getElementById("input-text").addEventListener("mousemove", this.highlighter.inputMousemove.bind(this.highlighter));
  136. this.addMultiEventListener("#input-text", "mousedown dblclick select", this.highlighter.inputMousedown, this.highlighter);
  137. document.querySelector("#input-file .close").addEventListener("click", this.input.clearIoClick.bind(this.input));
  138. // Output
  139. document.getElementById("save-to-file").addEventListener("click", this.output.saveClick.bind(this.output));
  140. document.getElementById("copy-output").addEventListener("click", this.output.copyClick.bind(this.output));
  141. document.getElementById("switch").addEventListener("click", this.output.switchClick.bind(this.output));
  142. document.getElementById("undo-switch").addEventListener("click", this.output.undoSwitchClick.bind(this.output));
  143. document.getElementById("maximise-output").addEventListener("click", this.output.maximiseOutputClick.bind(this.output));
  144. document.getElementById("output-text").addEventListener("scroll", this.highlighter.outputScroll.bind(this.highlighter));
  145. document.getElementById("output-text").addEventListener("mouseup", this.highlighter.outputMouseup.bind(this.highlighter));
  146. document.getElementById("output-text").addEventListener("mousemove", this.highlighter.outputMousemove.bind(this.highlighter));
  147. document.getElementById("output-html").addEventListener("mouseup", this.highlighter.outputHtmlMouseup.bind(this.highlighter));
  148. document.getElementById("output-html").addEventListener("mousemove", this.highlighter.outputHtmlMousemove.bind(this.highlighter));
  149. this.addMultiEventListener("#output-text", "mousedown dblclick select", this.highlighter.outputMousedown, this.highlighter);
  150. this.addMultiEventListener("#output-html", "mousedown dblclick select", this.highlighter.outputHtmlMousedown, this.highlighter);
  151. this.addDynamicListener("#output-file-download", "click", this.output.downloadFile, this.output);
  152. this.addDynamicListener("#output-file-slice", "click", this.output.displayFileSlice, this.output);
  153. document.getElementById("show-file-overlay").addEventListener("click", this.output.showFileOverlayClick.bind(this.output));
  154. // Options
  155. document.getElementById("options").addEventListener("click", this.options.optionsClick.bind(this.options));
  156. document.getElementById("reset-options").addEventListener("click", this.options.resetOptionsClick.bind(this.options));
  157. $(document).on("switchChange.bootstrapSwitch", ".option-item input:checkbox", this.options.switchChange.bind(this.options));
  158. $(document).on("switchChange.bootstrapSwitch", ".option-item input:checkbox", this.options.setWordWrap.bind(this.options));
  159. $(document).on("switchChange.bootstrapSwitch", ".option-item input:checkbox#useMetaKey", this.bindings.updateKeybList.bind(this.bindings));
  160. this.addDynamicListener(".option-item input[type=number]", "keyup", this.options.numberChange, this.options);
  161. this.addDynamicListener(".option-item input[type=number]", "change", this.options.numberChange, this.options);
  162. this.addDynamicListener(".option-item select", "change", this.options.selectChange, this.options);
  163. document.getElementById("theme").addEventListener("change", this.options.themeChange.bind(this.options));
  164. document.getElementById("logLevel").addEventListener("change", this.options.logLevelChange.bind(this.options));
  165. // Misc
  166. window.addEventListener("keydown", this.bindings.parseInput.bind(this.bindings));
  167. document.getElementById("alert-close").addEventListener("click", this.app.alertCloseClick.bind(this.app));
  168. }
  169. /**
  170. * Adds an event listener to each element in the specified group.
  171. *
  172. * @param {string} selector - A selector string for the element group to add the event to, see
  173. * this.getAll()
  174. * @param {string} eventType - The event to listen for
  175. * @param {function} callback - The function to execute when the event is triggered
  176. * @param {Object} [scope=this] - The object to bind to the callback function
  177. *
  178. * @example
  179. * // Calls the clickable function whenever any element with the .clickable class is clicked
  180. * this.addListeners(".clickable", "click", this.clickable, this);
  181. */
  182. addListeners(selector, eventType, callback, scope) {
  183. scope = scope || this;
  184. [].forEach.call(document.querySelectorAll(selector), function(el) {
  185. el.addEventListener(eventType, callback.bind(scope));
  186. });
  187. }
  188. /**
  189. * Adds multiple event listeners to the specified element.
  190. *
  191. * @param {string} selector - A selector string for the element to add the events to
  192. * @param {string} eventTypes - A space-separated string of all the event types to listen for
  193. * @param {function} callback - The function to execute when the events are triggered
  194. * @param {Object} [scope=this] - The object to bind to the callback function
  195. *
  196. * @example
  197. * // Calls the search function whenever the the keyup, paste or search events are triggered on the
  198. * // search element
  199. * this.addMultiEventListener("search", "keyup paste search", this.search, this);
  200. */
  201. addMultiEventListener(selector, eventTypes, callback, scope) {
  202. const evs = eventTypes.split(" ");
  203. for (let i = 0; i < evs.length; i++) {
  204. document.querySelector(selector).addEventListener(evs[i], callback.bind(scope));
  205. }
  206. }
  207. /**
  208. * Adds multiple event listeners to each element in the specified group.
  209. *
  210. * @param {string} selector - A selector string for the element group to add the events to
  211. * @param {string} eventTypes - A space-separated string of all the event types to listen for
  212. * @param {function} callback - The function to execute when the events are triggered
  213. * @param {Object} [scope=this] - The object to bind to the callback function
  214. *
  215. * @example
  216. * // Calls the save function whenever the the keyup or paste events are triggered on any element
  217. * // with the .saveable class
  218. * this.addMultiEventListener(".saveable", "keyup paste", this.save, this);
  219. */
  220. addMultiEventListeners(selector, eventTypes, callback, scope) {
  221. const evs = eventTypes.split(" ");
  222. for (let i = 0; i < evs.length; i++) {
  223. this.addListeners(selector, evs[i], callback, scope);
  224. }
  225. }
  226. /**
  227. * Adds an event listener to the global document object which will listen on dynamic elements which
  228. * may not exist in the DOM yet.
  229. *
  230. * @param {string} selector - A selector string for the element(s) to add the event to
  231. * @param {string} eventType - The event(s) to listen for
  232. * @param {function} callback - The function to execute when the event(s) is/are triggered
  233. * @param {Object} [scope=this] - The object to bind to the callback function
  234. *
  235. * @example
  236. * // Pops up an alert whenever any button is clicked, even if it is added to the DOM after this
  237. * // listener is created
  238. * this.addDynamicListener("button", "click", alert, this);
  239. */
  240. addDynamicListener(selector, eventType, callback, scope) {
  241. const eventConfig = {
  242. selector: selector,
  243. callback: callback.bind(scope || this)
  244. };
  245. if (this.dynamicHandlers.hasOwnProperty(eventType)) {
  246. // Listener already exists, add new handler to the appropriate list
  247. this.dynamicHandlers[eventType].push(eventConfig);
  248. } else {
  249. this.dynamicHandlers[eventType] = [eventConfig];
  250. // Set up listener for this new type
  251. document.addEventListener(eventType, this.dynamicListenerHandler.bind(this));
  252. }
  253. }
  254. /**
  255. * Handler for dynamic events. This function is called for any dynamic event and decides which
  256. * callback(s) to execute based on the type and selector.
  257. *
  258. * @param {Event} e - The event to be handled
  259. */
  260. dynamicListenerHandler(e) {
  261. const { type, target } = e;
  262. const handlers = this.dynamicHandlers[type];
  263. const matches = target.matches ||
  264. target.webkitMatchesSelector ||
  265. target.mozMatchesSelector ||
  266. target.msMatchesSelector ||
  267. target.oMatchesSelector;
  268. for (let i = 0; i < handlers.length; i++) {
  269. if (matches && matches.call(target, handlers[i].selector)) {
  270. handlers[i].callback(e);
  271. }
  272. }
  273. }
  274. }
  275. export default Manager;