2018-05-15 17:36:45 +00:00
/ * *
* @ author n1474335 [ n1474335 @ gmail . com ]
* @ copyright Crown Copyright 2016
* @ license Apache - 2.0
* /
2019-07-09 12:23:59 +01:00
import WorkerWaiter from "./waiters/WorkerWaiter.mjs" ;
import WindowWaiter from "./waiters/WindowWaiter.mjs" ;
import ControlsWaiter from "./waiters/ControlsWaiter.mjs" ;
import RecipeWaiter from "./waiters/RecipeWaiter.mjs" ;
import OperationsWaiter from "./waiters/OperationsWaiter.mjs" ;
import InputWaiter from "./waiters/InputWaiter.mjs" ;
import OutputWaiter from "./waiters/OutputWaiter.mjs" ;
import OptionsWaiter from "./waiters/OptionsWaiter.mjs" ;
import HighlighterWaiter from "./waiters/HighlighterWaiter.mjs" ;
import SeasonalWaiter from "./waiters/SeasonalWaiter.mjs" ;
import BindingsWaiter from "./waiters/BindingsWaiter.mjs" ;
import BackgroundWorkerWaiter from "./waiters/BackgroundWorkerWaiter.mjs" ;
import TabWaiter from "./waiters/TabWaiter.mjs" ;
2023-02-03 14:55:15 +00:00
import TimingWaiter from "./waiters/TimingWaiter.mjs" ;
2018-05-15 17:36:45 +00:00
/ * *
* This object controls the Waiters responsible for handling events from all areas of the app .
* /
class Manager {
/ * *
* Manager constructor .
*
* @ param { App } app - The main view object for CyberChef .
* /
constructor ( app ) {
this . app = app ;
// Define custom events
/ * *
* @ event Manager # appstart
* /
this . appstart = new CustomEvent ( "appstart" , { bubbles : true } ) ;
/ * *
* @ event Manager # apploaded
* /
this . apploaded = new CustomEvent ( "apploaded" , { bubbles : true } ) ;
/ * *
* @ event Manager # operationadd
* /
this . operationadd = new CustomEvent ( "operationadd" , { bubbles : true } ) ;
/ * *
* @ event Manager # operationremove
* /
this . operationremove = new CustomEvent ( "operationremove" , { bubbles : true } ) ;
/ * *
* @ event Manager # statechange
* /
this . statechange = new CustomEvent ( "statechange" , { bubbles : true } ) ;
// Define Waiter objects to handle various areas
2023-02-03 14:55:15 +00:00
this . timing = new TimingWaiter ( this . app , this ) ;
2018-05-15 17:36:45 +00:00
this . worker = new WorkerWaiter ( this . app , this ) ;
2023-05-09 14:56:44 +12:00
this . window = new WindowWaiter ( this . app , this ) ;
2018-05-15 17:36:45 +00:00
this . controls = new ControlsWaiter ( this . app , this ) ;
this . recipe = new RecipeWaiter ( this . app , this ) ;
this . ops = new OperationsWaiter ( this . app , this ) ;
2019-06-07 13:52:04 +01:00
this . tabs = new TabWaiter ( this . app , this ) ;
2018-05-15 17:36:45 +00:00
this . input = new InputWaiter ( this . app , this ) ;
this . output = new OutputWaiter ( this . app , this ) ;
this . options = new OptionsWaiter ( this . app , this ) ;
this . highlighter = new HighlighterWaiter ( this . app , this ) ;
this . seasonal = new SeasonalWaiter ( this . app , this ) ;
this . bindings = new BindingsWaiter ( this . app , this ) ;
2018-06-03 17:33:13 +01:00
this . background = new BackgroundWorkerWaiter ( this . app , this ) ;
2018-05-15 17:36:45 +00:00
// Object to store dynamic handlers to fire on elements that may not exist yet
this . dynamicHandlers = { } ;
this . initialiseEventListeners ( ) ;
}
/ * *
* Sets up the various components and listeners .
* /
setup ( ) {
2019-04-25 16:32:48 +01:00
this . input . setupInputWorker ( ) ;
2019-04-30 13:18:22 +01:00
this . input . addInput ( true ) ;
2019-04-02 16:58:36 +01:00
this . worker . setupChefWorker ( ) ;
2023-08-02 17:38:52 +12:00
this . recipe . initDragAndDrop ( ) ;
2018-06-09 10:43:36 +01:00
this . controls . initComponents ( ) ;
2018-05-15 17:36:45 +00:00
this . controls . autoBakeChange ( ) ;
this . bindings . updateKeybList ( ) ;
2018-06-03 17:33:13 +01:00
this . background . registerChefWorker ( ) ;
2018-05-15 17:36:45 +00:00
this . seasonal . load ( ) ;
2022-10-21 18:29:52 +01:00
this . confirmWaitersLoaded ( ) ;
}
/ * *
* Confirms that all Waiters have loaded correctly .
* /
confirmWaitersLoaded ( ) {
if ( this . tabs . getActiveTab ( "input" ) >= 0 &&
this . tabs . getActiveTab ( "output" ) >= 0 ) {
log . debug ( "Waiters loaded" ) ;
this . app . waitersLoaded = true ;
this . app . loaded ( ) ;
} else {
// Not loaded yet, try again soon
setTimeout ( this . confirmWaitersLoaded . bind ( this ) , 10 ) ;
}
2018-05-15 17:36:45 +00:00
}
/ * *
* Main function to handle the creation of the event listeners .
* /
initialiseEventListeners ( ) {
// Global
window . addEventListener ( "resize" , this . window . windowResize . bind ( this . window ) ) ;
window . addEventListener ( "blur" , this . window . windowBlur . bind ( this . window ) ) ;
window . addEventListener ( "focus" , this . window . windowFocus . bind ( this . window ) ) ;
window . addEventListener ( "statechange" , this . app . stateChange . bind ( this . app ) ) ;
window . addEventListener ( "popstate" , this . app . popState . bind ( this . app ) ) ;
// Controls
document . getElementById ( "bake" ) . addEventListener ( "click" , this . controls . bakeClick . bind ( this . controls ) ) ;
document . getElementById ( "auto-bake" ) . addEventListener ( "change" , this . controls . autoBakeChange . bind ( this . controls ) ) ;
document . getElementById ( "step" ) . addEventListener ( "click" , this . controls . stepClick . bind ( this . controls ) ) ;
document . getElementById ( "clr-recipe" ) . addEventListener ( "click" , this . controls . clearRecipeClick . bind ( this . controls ) ) ;
document . getElementById ( "save" ) . addEventListener ( "click" , this . controls . saveClick . bind ( this . controls ) ) ;
document . getElementById ( "save-button" ) . addEventListener ( "click" , this . controls . saveButtonClick . bind ( this . controls ) ) ;
document . getElementById ( "save-link-recipe-checkbox" ) . addEventListener ( "change" , this . controls . slrCheckChange . bind ( this . controls ) ) ;
document . getElementById ( "save-link-input-checkbox" ) . addEventListener ( "change" , this . controls . sliCheckChange . bind ( this . controls ) ) ;
document . getElementById ( "load" ) . addEventListener ( "click" , this . controls . loadClick . bind ( this . controls ) ) ;
document . getElementById ( "load-delete-button" ) . addEventListener ( "click" , this . controls . loadDeleteClick . bind ( this . controls ) ) ;
document . getElementById ( "load-name" ) . addEventListener ( "change" , this . controls . loadNameChange . bind ( this . controls ) ) ;
document . getElementById ( "load-button" ) . addEventListener ( "click" , this . controls . loadButtonClick . bind ( this . controls ) ) ;
document . getElementById ( "support" ) . addEventListener ( "click" , this . controls . supportButtonClick . bind ( this . controls ) ) ;
this . addMultiEventListeners ( "#save-texts textarea" , "keyup paste" , this . controls . saveTextChange , this . controls ) ;
2023-05-15 20:54:49 +12:00
document . getElementById ( "rec-list" ) . addEventListener ( "click" , this . controls . onMaximisedRecipeClick . bind ( this . controls ) ) ;
2023-05-09 14:56:44 +12:00
// A note for the Maximise Controls listeners below: click events via addDynamicListener don't properly bubble and the hit box to maximise is unacceptably tiny, hence this solution
2023-05-10 21:53:29 +12:00
document . getElementById ( "maximise-recipe" ) . addEventListener ( "click" , this . controls . onMaximiseButtonClick . bind ( this . controls ) ) ;
document . getElementById ( "maximise-input" ) . addEventListener ( "click" , this . controls . onMaximiseButtonClick . bind ( this . controls ) ) ;
document . getElementById ( "maximise-output" ) . addEventListener ( "click" , this . controls . onMaximiseButtonClick . bind ( this . controls ) ) ;
2018-05-15 17:36:45 +00:00
// Operations
2023-08-07 16:42:06 +12:00
this . addMultiEventListener ( "#search" , "keyup paste click" , this . ops . searchOperations , this . ops ) ;
2023-05-23 20:05:27 +12:00
document . getElementById ( "close-ops-dropdown-icon" ) . addEventListener ( "click" , this . ops . closeOpsDropdown . bind ( this . ops ) ) ;
2018-05-15 17:36:45 +00:00
document . getElementById ( "save-favourites" ) . addEventListener ( "click" , this . ops . saveFavouritesClick . bind ( this . ops ) ) ;
document . getElementById ( "reset-favourites" ) . addEventListener ( "click" , this . ops . resetFavouritesClick . bind ( this . ops ) ) ;
2023-08-02 17:38:52 +12:00
this . addDynamicListener ( "c-operation-li" , "operationadd" , this . recipe . opAdd , this . recipe ) ;
2018-05-15 17:36:45 +00:00
// Recipe
this . addDynamicListener ( ".arg:not(select)" , "input" , this . recipe . ingChange , this . recipe ) ;
this . addDynamicListener ( ".arg[type=checkbox], .arg[type=radio], select.arg" , "change" , this . recipe . ingChange , this . recipe ) ;
this . addDynamicListener ( ".breakpoint" , "click" , this . recipe . breakpointClick , this . recipe ) ;
2018-06-10 12:03:55 +01:00
this . addDynamicListener ( "#rec-list .dropdown-menu.toggle-dropdown a" , "click" , this . recipe . dropdownToggleClick , this . recipe ) ;
2019-01-15 23:42:05 +00:00
this . addDynamicListener ( "textarea.arg" , "dragover" , this . recipe . textArgDragover , this . recipe ) ;
this . addDynamicListener ( "textarea.arg" , "dragleave" , this . recipe . textArgDragLeave , this . recipe ) ;
this . addDynamicListener ( "textarea.arg" , "drop" , this . recipe . textArgDrop , this . recipe ) ;
2018-05-15 17:36:45 +00:00
// Input
document . getElementById ( "reset-layout" ) . addEventListener ( "click" , this . app . resetLayout . bind ( this . app ) ) ;
2019-06-03 15:12:59 +01:00
this . addListeners ( "#clr-io,#btn-close-all-tabs" , "click" , this . input . clearAllIoClick , this . input ) ;
2019-04-25 16:32:48 +01:00
this . addListeners ( "#open-file,#open-folder" , "change" , this . input . inputOpen , this . input ) ;
2023-01-13 16:46:41 +00:00
document . getElementById ( "btn-open-file" ) . addEventListener ( "click" , this . input . inputOpenClick . bind ( this . input ) ) ;
document . getElementById ( "btn-open-folder" ) . addEventListener ( "click" , this . input . folderOpenClick . bind ( this . input ) ) ;
2022-07-11 13:57:28 +01:00
this . addListeners ( "#input-wrapper" , "dragover" , this . input . inputDragover , this . input ) ;
this . addListeners ( "#input-wrapper" , "dragleave" , this . input . inputDragleave , this . input ) ;
this . addListeners ( "#input-wrapper" , "drop" , this . input . inputDrop , this . input ) ;
2019-04-30 13:18:22 +01:00
document . getElementById ( "btn-new-tab" ) . addEventListener ( "click" , this . input . addInputClick . bind ( this . input ) ) ;
2019-06-04 11:42:27 +01:00
document . getElementById ( "btn-previous-input-tab" ) . addEventListener ( "mousedown" , this . input . previousTabClick . bind ( this . input ) ) ;
document . getElementById ( "btn-next-input-tab" ) . addEventListener ( "mousedown" , this . input . nextTabClick . bind ( this . input ) ) ;
this . addListeners ( "#btn-next-input-tab,#btn-previous-input-tab" , "mouseup" , this . input . tabMouseUp , this . input ) ;
this . addListeners ( "#btn-next-input-tab,#btn-previous-input-tab" , "mouseout" , this . input . tabMouseUp , this . input ) ;
2019-04-26 15:15:44 +01:00
document . getElementById ( "btn-go-to-input-tab" ) . addEventListener ( "click" , this . input . goToTab . bind ( this . input ) ) ;
2019-04-30 14:45:34 +01:00
document . getElementById ( "btn-find-input-tab" ) . addEventListener ( "click" , this . input . findTab . bind ( this . input ) ) ;
2019-04-01 16:15:09 +01:00
this . addDynamicListener ( "#input-tabs li .input-tab-content" , "click" , this . input . changeTabClick , this . input ) ;
2019-04-30 14:45:34 +01:00
document . getElementById ( "input-show-pending" ) . addEventListener ( "change" , this . input . filterTabSearch . bind ( this . input ) ) ;
document . getElementById ( "input-show-loading" ) . addEventListener ( "change" , this . input . filterTabSearch . bind ( this . input ) ) ;
document . getElementById ( "input-show-loaded" ) . addEventListener ( "change" , this . input . filterTabSearch . bind ( this . input ) ) ;
2019-05-30 13:28:45 +01:00
this . addListeners ( "#input-filter-content,#input-filter-filename" , "click" , this . input . filterOptionClick , this . input ) ;
document . getElementById ( "input-filter" ) . addEventListener ( "change" , this . input . filterTabSearch . bind ( this . input ) ) ;
document . getElementById ( "input-filter" ) . addEventListener ( "keyup" , this . input . filterTabSearch . bind ( this . input ) ) ;
2019-04-30 14:45:34 +01:00
document . getElementById ( "input-num-results" ) . addEventListener ( "change" , this . input . filterTabSearch . bind ( this . input ) ) ;
document . getElementById ( "input-num-results" ) . addEventListener ( "keyup" , this . input . filterTabSearch . bind ( this . input ) ) ;
document . getElementById ( "input-filter-refresh" ) . addEventListener ( "click" , this . input . filterTabSearch . bind ( this . input ) ) ;
this . addDynamicListener ( ".input-filter-result" , "click" , this . input . filterItemClick , this . input ) ;
2019-04-25 16:32:48 +01:00
2018-05-15 17:36:45 +00:00
// Output
2019-04-03 12:00:47 +01:00
document . getElementById ( "save-to-file" ) . addEventListener ( "click" , this . output . saveClick . bind ( this . output ) ) ;
document . getElementById ( "save-all-to-file" ) . addEventListener ( "click" , this . output . saveAllClick . bind ( this . output ) ) ;
2019-04-25 16:32:48 +01:00
document . getElementById ( "copy-output" ) . addEventListener ( "click" , this . output . copyClick . bind ( this . output ) ) ;
2019-05-07 15:34:36 +01:00
document . getElementById ( "switch" ) . addEventListener ( "click" , this . output . switchClick . bind ( this . output ) ) ;
2019-04-25 16:32:48 +01:00
document . getElementById ( "magic" ) . addEventListener ( "click" , this . output . magicClick . bind ( this . output ) ) ;
2019-05-29 16:29:34 +01:00
this . addDynamicListener ( ".extract-file,.extract-file i" , "click" , this . output . extractFileClick , this . output ) ;
2019-04-02 16:58:36 +01:00
this . addDynamicListener ( "#output-tabs-wrapper #output-tabs li .output-tab-content" , "click" , this . output . changeTabClick , this . output ) ;
2019-06-04 11:42:27 +01:00
document . getElementById ( "btn-previous-output-tab" ) . addEventListener ( "mousedown" , this . output . previousTabClick . bind ( this . output ) ) ;
document . getElementById ( "btn-next-output-tab" ) . addEventListener ( "mousedown" , this . output . nextTabClick . bind ( this . output ) ) ;
this . addListeners ( "#btn-next-output-tab,#btn-previous-output-tab" , "mouseup" , this . output . tabMouseUp , this . output ) ;
this . addListeners ( "#btn-next-output-tab,#btn-previous-output-tab" , "mouseout" , this . output . tabMouseUp , this . output ) ;
2019-04-03 12:00:47 +01:00
document . getElementById ( "btn-go-to-output-tab" ) . addEventListener ( "click" , this . output . goToTab . bind ( this . output ) ) ;
2019-05-15 09:37:07 +01:00
document . getElementById ( "btn-find-output-tab" ) . addEventListener ( "click" , this . output . findTab . bind ( this . output ) ) ;
document . getElementById ( "output-show-pending" ) . addEventListener ( "change" , this . output . filterTabSearch . bind ( this . output ) ) ;
document . getElementById ( "output-show-baking" ) . addEventListener ( "change" , this . output . filterTabSearch . bind ( this . output ) ) ;
document . getElementById ( "output-show-baked" ) . addEventListener ( "change" , this . output . filterTabSearch . bind ( this . output ) ) ;
2019-05-15 16:24:49 +01:00
document . getElementById ( "output-show-stale" ) . addEventListener ( "change" , this . output . filterTabSearch . bind ( this . output ) ) ;
document . getElementById ( "output-show-errored" ) . addEventListener ( "change" , this . output . filterTabSearch . bind ( this . output ) ) ;
2019-05-15 09:37:07 +01:00
document . getElementById ( "output-content-filter" ) . addEventListener ( "change" , this . output . filterTabSearch . bind ( this . output ) ) ;
document . getElementById ( "output-content-filter" ) . addEventListener ( "keyup" , this . output . filterTabSearch . bind ( this . output ) ) ;
document . getElementById ( "output-num-results" ) . addEventListener ( "change" , this . output . filterTabSearch . bind ( this . output ) ) ;
document . getElementById ( "output-num-results" ) . addEventListener ( "keyup" , this . output . filterTabSearch . bind ( this . output ) ) ;
document . getElementById ( "output-filter-refresh" ) . addEventListener ( "click" , this . output . filterTabSearch . bind ( this . output ) ) ;
2019-05-15 16:03:18 +01:00
this . addDynamicListener ( ".output-filter-result" , "click" , this . output . filterItemClick , this . output ) ;
2019-05-15 09:37:07 +01:00
2018-05-15 17:36:45 +00:00
// Options
document . getElementById ( "options" ) . addEventListener ( "click" , this . options . optionsClick . bind ( this . options ) ) ;
document . getElementById ( "reset-options" ) . addEventListener ( "click" , this . options . resetOptionsClick . bind ( this . options ) ) ;
2018-06-17 12:44:12 +01:00
this . addDynamicListener ( ".option-item input[type=checkbox]" , "change" , this . options . switchChange , this . options ) ;
2019-08-22 11:26:43 +01:00
this . addDynamicListener ( ".option-item input[type=checkbox]#wordWrap" , "change" , this . options . setWordWrap , this . options ) ;
2018-06-17 12:44:12 +01:00
this . addDynamicListener ( ".option-item input[type=checkbox]#useMetaKey" , "change" , this . bindings . updateKeybList , this . bindings ) ;
2018-05-15 17:36:45 +00:00
this . addDynamicListener ( ".option-item input[type=number]" , "keyup" , this . options . numberChange , this . options ) ;
this . addDynamicListener ( ".option-item input[type=number]" , "change" , this . options . numberChange , this . options ) ;
this . addDynamicListener ( ".option-item select" , "change" , this . options . selectChange , this . options ) ;
document . getElementById ( "theme" ) . addEventListener ( "change" , this . options . themeChange . bind ( this . options ) ) ;
document . getElementById ( "logLevel" ) . addEventListener ( "change" , this . options . logLevelChange . bind ( this . options ) ) ;
// Misc
window . addEventListener ( "keydown" , this . bindings . parseInput . bind ( this . bindings ) ) ;
}
/ * *
* Adds an event listener to each element in the specified group .
*
* @ param { string } selector - A selector string for the element group to add the event to , see
* this . getAll ( )
* @ param { string } eventType - The event to listen for
* @ param { function } callback - The function to execute when the event is triggered
* @ param { Object } [ scope = this ] - The object to bind to the callback function
*
* @ example
* // Calls the clickable function whenever any element with the .clickable class is clicked
* this . addListeners ( ".clickable" , "click" , this . clickable , this ) ;
* /
addListeners ( selector , eventType , callback , scope ) {
scope = scope || this ;
[ ] . forEach . call ( document . querySelectorAll ( selector ) , function ( el ) {
el . addEventListener ( eventType , callback . bind ( scope ) ) ;
} ) ;
}
/ * *
* Adds multiple event listeners to the specified element .
*
* @ param { string } selector - A selector string for the element to add the events to
* @ param { string } eventTypes - A space - separated string of all the event types to listen for
* @ param { function } callback - The function to execute when the events are triggered
* @ param { Object } [ scope = this ] - The object to bind to the callback function
*
* @ example
* // Calls the search function whenever the the keyup, paste or search events are triggered on the
* // search element
* this . addMultiEventListener ( "search" , "keyup paste search" , this . search , this ) ;
* /
addMultiEventListener ( selector , eventTypes , callback , scope ) {
const evs = eventTypes . split ( " " ) ;
for ( let i = 0 ; i < evs . length ; i ++ ) {
document . querySelector ( selector ) . addEventListener ( evs [ i ] , callback . bind ( scope ) ) ;
}
}
/ * *
* Adds multiple event listeners to each element in the specified group .
*
* @ param { string } selector - A selector string for the element group to add the events to
* @ param { string } eventTypes - A space - separated string of all the event types to listen for
* @ param { function } callback - The function to execute when the events are triggered
* @ param { Object } [ scope = this ] - The object to bind to the callback function
*
* @ example
2023-05-02 18:29:30 +12:00
* // Calls the save function whenever the keyup or paste events are triggered on any element
2018-05-15 17:36:45 +00:00
* // with the .saveable class
* this . addMultiEventListener ( ".saveable" , "keyup paste" , this . save , this ) ;
* /
addMultiEventListeners ( selector , eventTypes , callback , scope ) {
const evs = eventTypes . split ( " " ) ;
for ( let i = 0 ; i < evs . length ; i ++ ) {
this . addListeners ( selector , evs [ i ] , callback , scope ) ;
}
}
/ * *
* Adds an event listener to the global document object which will listen on dynamic elements which
* may not exist in the DOM yet .
*
* @ param { string } selector - A selector string for the element ( s ) to add the event to
* @ param { string } eventType - The event ( s ) to listen for
* @ param { function } callback - The function to execute when the event ( s ) is / are triggered
* @ param { Object } [ scope = this ] - The object to bind to the callback function
*
* @ example
* // Pops up an alert whenever any button is clicked, even if it is added to the DOM after this
* // listener is created
* this . addDynamicListener ( "button" , "click" , alert , this ) ;
* /
addDynamicListener ( selector , eventType , callback , scope ) {
const eventConfig = {
selector : selector ,
callback : callback . bind ( scope || this )
} ;
2019-07-05 12:22:52 +01:00
if ( Object . prototype . hasOwnProperty . call ( this . dynamicHandlers , eventType ) ) {
2018-05-15 17:36:45 +00:00
// Listener already exists, add new handler to the appropriate list
this . dynamicHandlers [ eventType ] . push ( eventConfig ) ;
} else {
this . dynamicHandlers [ eventType ] = [ eventConfig ] ;
// Set up listener for this new type
document . addEventListener ( eventType , this . dynamicListenerHandler . bind ( this ) ) ;
}
}
/ * *
* Handler for dynamic events . This function is called for any dynamic event and decides which
* callback ( s ) to execute based on the type and selector .
*
* @ param { Event } e - The event to be handled
* /
dynamicListenerHandler ( e ) {
const { type , target } = e ;
const handlers = this . dynamicHandlers [ type ] ;
const matches = target . matches ||
target . webkitMatchesSelector ||
target . mozMatchesSelector ||
target . msMatchesSelector ||
target . oMatchesSelector ;
for ( let i = 0 ; i < handlers . length ; i ++ ) {
if ( matches && matches . call ( target , handlers [ i ] . selector ) ) {
handlers [ i ] . callback ( e ) ;
}
}
}
}
export default Manager ;