Merge branch 'master' into hide-recipe-options

This commit is contained in:
TheZ3ro 2024-04-05 13:23:28 +02:00 committed by GitHub
commit a1892d4411
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
443 changed files with 42231 additions and 17174 deletions

View file

@ -4,7 +4,7 @@
* @license Apache-2.0
*/
import ChefWorker from "worker-loader?inline&fallback=false!../../core/ChefWorker.js";
import ChefWorker from "worker-loader?inline=no-fallback!../../core/ChefWorker.js";
/**
* Waiter to handle conversations with a ChefWorker in the background.
@ -35,6 +35,14 @@ class BackgroundWorkerWaiter {
log.debug("Registering new background ChefWorker");
this.chefWorker = new ChefWorker();
this.chefWorker.addEventListener("message", this.handleChefMessage.bind(this));
this.chefWorker.postMessage({
action: "setLogPrefix",
data: "BGChefWorker"
});
this.chefWorker.postMessage({
action: "setLogLevel",
data: log.getLevel()
});
let docURL = document.location.href.split(/[#?]/)[0];
const index = docURL.lastIndexOf("/");
@ -52,7 +60,7 @@ class BackgroundWorkerWaiter {
*/
handleChefMessage(e) {
const r = e.data;
log.debug("Receiving '" + r.action + "' from ChefWorker in the background");
log.debug(`Receiving '${r.action}' from BGChefWorker`);
switch (r.action) {
case "bakeComplete":
@ -152,6 +160,18 @@ class BackgroundWorkerWaiter {
this.manager.output.backgroundMagicResult(response.dish.value);
}
/**
* Sets the console log level in the workers.
*/
setLogLevel() {
if (!this.chefWorker) return;
this.chefWorker.postMessage({
action: "setLogLevel",
data: log.getLevel()
});
}
}

View file

@ -40,11 +40,11 @@ class BindingsWaiter {
break;
case "KeyI": // Focus input
e.preventDefault();
document.getElementById("input-text").focus();
this.manager.input.inputEditorView.focus();
break;
case "KeyO": // Focus output
e.preventDefault();
document.getElementById("output-text").focus();
this.manager.output.outputEditorView.focus();
break;
case "Period": // Focus next operation
e.preventDefault();
@ -126,7 +126,7 @@ class BindingsWaiter {
break;
case "KeyW": // Close tab
e.preventDefault();
this.manager.input.removeInput(this.manager.tabs.getActiveInputTab());
this.manager.input.removeInput(this.manager.tabs.getActiveTab("input"));
break;
case "ArrowLeft": // Go to previous tab
e.preventDefault();
@ -148,6 +148,13 @@ class BindingsWaiter {
}
break;
}
} else {
switch (e.code) {
case "F1":
e.preventDefault();
this.contextualHelp();
break;
}
}
}
@ -164,9 +171,14 @@ class BindingsWaiter {
}
document.getElementById("keybList").innerHTML = `
<tr>
<td><b>Command</b></td>
<td><b>Shortcut (Win/Linux)</b></td>
<td><b>Shortcut (Mac)</b></td>
<th>Command</th>
<th>Shortcut (Win/Linux)</th>
<th>Shortcut (Mac)</th>
</tr>
<tr>
<td>Activate contextual help</td>
<td>F1</td>
<td>F1</td>
</tr>
<tr>
<td>Place cursor in search field</td>
@ -255,6 +267,42 @@ class BindingsWaiter {
`;
}
/**
* Shows contextual help message based on where the mouse pointer is
*/
contextualHelp() {
const hoveredHelpEls = document.querySelectorAll(":hover[data-help],:hover[data-help-proxy]");
if (!hoveredHelpEls.length) return;
let helpEl = hoveredHelpEls[hoveredHelpEls.length - 1];
const helpElSelector = helpEl.getAttribute("data-help-proxy");
if (helpElSelector) {
// A hovered element is directing us to another element for its help text
helpEl = document.querySelector(helpElSelector);
}
this.displayHelp(helpEl);
}
/**
* Displays the help pane populated with help text associated with the given element
*
* @param {Element} el
*/
displayHelp(el) {
const helpText = el.getAttribute("data-help");
let helpTitle = el.getAttribute("data-help-title");
if (helpTitle)
helpTitle = "<span class='text-muted'>Help topic:</span> " + helpTitle;
else
helpTitle = "<span class='text-muted'>Help topic</span>";
document.querySelector("#help-modal .modal-body").innerHTML = helpText;
document.querySelector("#help-modal #help-title").innerHTML = helpTitle;
$("#help-modal").modal();
}
}
export default BindingsWaiter;

View file

@ -5,6 +5,7 @@
*/
import Utils from "../../core/Utils.mjs";
import { eolSeqToCode } from "../utils/editorUtils.mjs";
/**
@ -100,9 +101,9 @@ class ControlsWaiter {
const includeRecipe = document.getElementById("save-link-recipe-checkbox").checked;
const includeInput = document.getElementById("save-link-input-checkbox").checked;
const saveLinkEl = document.getElementById("save-link");
const saveLink = this.generateStateUrl(includeRecipe, includeInput, recipeConfig);
const saveLink = this.generateStateUrl(includeRecipe, includeInput, null, recipeConfig);
saveLinkEl.innerHTML = Utils.truncate(saveLink, 120);
saveLinkEl.innerHTML = Utils.escapeHtml(Utils.truncate(saveLink, 120));
saveLinkEl.setAttribute("href", saveLink);
}
@ -128,17 +129,28 @@ class ControlsWaiter {
includeRecipe = includeRecipe && (recipeConfig.length > 0);
// If we don't get passed an input, get it from the current URI
if (input === null) {
if (input === null && includeInput) {
const params = this.app.getURIParams();
if (params.input) {
includeInput = true;
input = params.input;
} else {
includeInput = false;
}
}
const inputChrEnc = this.manager.input.getChrEnc();
const outputChrEnc = this.manager.output.getChrEnc();
const inputEOL = eolSeqToCode[this.manager.input.getEOLSeq()];
const outputEOL = eolSeqToCode[this.manager.output.getEOLSeq()];
const params = [
includeRecipe ? ["recipe", recipeStr] : undefined,
includeInput ? ["input", input] : undefined,
includeInput && input.length ? ["input", Utils.escapeHtml(input)] : undefined,
inputChrEnc !== 0 ? ["ienc", inputChrEnc] : undefined,
outputChrEnc !== 0 ? ["oenc", outputChrEnc] : undefined,
inputEOL !== "LF" ? ["ieol", inputEOL] : undefined,
outputEOL !== "LF" ? ["oeol", outputEOL] : undefined
];
const hash = params
@ -438,6 +450,17 @@ ${navigator.userAgent}
}
}
/**
* Calculates the height of the controls area and adjusts the recipe
* height accordingly.
*/
calcControlsHeight() {
const controls = document.getElementById("controls"),
recList = document.getElementById("rec-list");
recList.style.bottom = controls.clientHeight + "px";
}
}
export default ControlsWaiter;

View file

@ -4,18 +4,8 @@
* @license Apache-2.0
*/
/**
* HighlighterWaiter data type enum for the input.
* @enum
*/
const INPUT = 0;
/**
* HighlighterWaiter data type enum for the output.
* @enum
*/
const OUTPUT = 1;
import {EditorSelection} from "@codemirror/state";
import {chrEncWidth} from "../../core/lib/ChrEnc.mjs";
/**
* Waiter to handle events related to highlighting in CyberChef.
@ -32,436 +22,115 @@ class HighlighterWaiter {
this.app = app;
this.manager = manager;
this.mouseButtonDown = false;
this.mouseTarget = null;
this.currentSelectionRanges = [];
}
/**
* Determines if the current text selection is running backwards or forwards.
* StackOverflow answer id: 12652116
* Handler for selection change events in the input and output
*
* @private
* @returns {boolean}
*/
_isSelectionBackwards() {
let backwards = false;
const sel = window.getSelection();
if (!sel.isCollapsed) {
const range = document.createRange();
range.setStart(sel.anchorNode, sel.anchorOffset);
range.setEnd(sel.focusNode, sel.focusOffset);
backwards = range.collapsed;
range.detach();
}
return backwards;
}
/**
* Calculates the text offset of a position in an HTML element, ignoring HTML tags.
*
* @private
* @param {element} node - The parent HTML node.
* @param {number} offset - The offset since the last HTML element.
* @returns {number}
*/
_getOutputHtmlOffset(node, offset) {
const sel = window.getSelection();
const range = document.createRange();
range.selectNodeContents(document.getElementById("output-html"));
range.setEnd(node, offset);
sel.removeAllRanges();
sel.addRange(range);
return sel.toString().length;
}
/**
* Gets the current selection offsets in the output HTML, ignoring HTML tags.
*
* @private
* @returns {Object} pos
* @returns {number} pos.start
* @returns {number} pos.end
*/
_getOutputHtmlSelectionOffsets() {
const sel = window.getSelection();
let range,
start = 0,
end = 0,
backwards = false;
if (sel.rangeCount) {
range = sel.getRangeAt(sel.rangeCount - 1);
backwards = this._isSelectionBackwards();
start = this._getOutputHtmlOffset(range.startContainer, range.startOffset);
end = this._getOutputHtmlOffset(range.endContainer, range.endOffset);
sel.removeAllRanges();
sel.addRange(range);
if (backwards) {
// If selecting backwards, reverse the start and end offsets for the selection to
// prevent deselecting as the drag continues.
sel.collapseToEnd();
sel.extend(sel.anchorNode, range.startOffset);
}
}
return {
start: start,
end: end
};
}
/**
* Handler for input scroll events.
* Scrolls the highlighter pane to match the input textarea position.
*
* @param {event} e
*/
inputScroll(e) {
const el = e.target;
document.getElementById("input-highlighter").scrollTop = el.scrollTop;
document.getElementById("input-highlighter").scrollLeft = el.scrollLeft;
}
/**
* Handler for output scroll events.
* Scrolls the highlighter pane to match the output textarea position.
*
* @param {event} e
*/
outputScroll(e) {
const el = e.target;
document.getElementById("output-highlighter").scrollTop = el.scrollTop;
document.getElementById("output-highlighter").scrollLeft = el.scrollLeft;
}
/**
* Handler for input mousedown events.
* Calculates the current selection info, and highlights the corresponding data in the output.
*
* @param {event} e
*/
inputMousedown(e) {
this.mouseButtonDown = true;
this.mouseTarget = INPUT;
this.removeHighlights();
const el = e.target;
const start = el.selectionStart;
const end = el.selectionEnd;
if (start !== 0 || end !== 0) {
document.getElementById("input-selection-info").innerHTML = this.selectionInfo(start, end);
this.highlightOutput([{start: start, end: end}]);
}
}
/**
* Handler for output mousedown events.
* Calculates the current selection info, and highlights the corresponding data in the input.
*
* @param {event} e
*/
outputMousedown(e) {
this.mouseButtonDown = true;
this.mouseTarget = OUTPUT;
this.removeHighlights();
const el = e.target;
const start = el.selectionStart;
const end = el.selectionEnd;
if (start !== 0 || end !== 0) {
document.getElementById("output-selection-info").innerHTML = this.selectionInfo(start, end);
this.highlightInput([{start: start, end: end}]);
}
}
/**
* Handler for output HTML mousedown events.
* Calculates the current selection info.
*
* @param {event} e
*/
outputHtmlMousedown(e) {
this.mouseButtonDown = true;
this.mouseTarget = OUTPUT;
const sel = this._getOutputHtmlSelectionOffsets();
if (sel.start !== 0 || sel.end !== 0) {
document.getElementById("output-selection-info").innerHTML = this.selectionInfo(sel.start, sel.end);
}
}
/**
* Handler for input mouseup events.
*
* @param {event} e
*/
inputMouseup(e) {
this.mouseButtonDown = false;
}
/**
* Handler for output mouseup events.
*
* @param {event} e
*/
outputMouseup(e) {
this.mouseButtonDown = false;
}
/**
* Handler for output HTML mouseup events.
*
* @param {event} e
*/
outputHtmlMouseup(e) {
this.mouseButtonDown = false;
}
/**
* Handler for input mousemove events.
* Calculates the current selection info, and highlights the corresponding data in the output.
*
* @param {event} e
*/
inputMousemove(e) {
// Check that the left mouse button is pressed
if (!this.mouseButtonDown ||
e.which !== 1 ||
this.mouseTarget !== INPUT)
return;
const el = e.target;
const start = el.selectionStart;
const end = el.selectionEnd;
if (start !== 0 || end !== 0) {
document.getElementById("input-selection-info").innerHTML = this.selectionInfo(start, end);
this.highlightOutput([{start: start, end: end}]);
}
}
/**
* Handler for output mousemove events.
* Calculates the current selection info, and highlights the corresponding data in the input.
*
* @param {event} e
*/
outputMousemove(e) {
// Check that the left mouse button is pressed
if (!this.mouseButtonDown ||
e.which !== 1 ||
this.mouseTarget !== OUTPUT)
return;
const el = e.target;
const start = el.selectionStart;
const end = el.selectionEnd;
if (start !== 0 || end !== 0) {
document.getElementById("output-selection-info").innerHTML = this.selectionInfo(start, end);
this.highlightInput([{start: start, end: end}]);
}
}
/**
* Handler for output HTML mousemove events.
* Calculates the current selection info.
*
* @param {event} e
*/
outputHtmlMousemove(e) {
// Check that the left mouse button is pressed
if (!this.mouseButtonDown ||
e.which !== 1 ||
this.mouseTarget !== OUTPUT)
return;
const sel = this._getOutputHtmlSelectionOffsets();
if (sel.start !== 0 || sel.end !== 0) {
document.getElementById("output-selection-info").innerHTML = this.selectionInfo(sel.start, sel.end);
}
}
/**
* Given start and end offsets, writes the HTML for the selection info element with the correct
* padding.
*
* @param {number} start - The start offset.
* @param {number} end - The end offset.
* @returns {string}
*/
selectionInfo(start, end) {
const len = end.toString().length;
const width = len < 2 ? 2 : len;
const startStr = start.toString().padStart(width, " ").replace(/ /g, "&nbsp;");
const endStr = end.toString().padStart(width, " ").replace(/ /g, "&nbsp;");
const lenStr = (end-start).toString().padStart(width, " ").replace(/ /g, "&nbsp;");
return "start: " + startStr + "<br>end: " + endStr + "<br>length: " + lenStr;
}
/**
* Removes highlighting and selection information.
*/
removeHighlights() {
document.getElementById("input-highlighter").innerHTML = "";
document.getElementById("output-highlighter").innerHTML = "";
document.getElementById("input-selection-info").innerHTML = "";
document.getElementById("output-selection-info").innerHTML = "";
}
/**
* Highlights the given offsets in the output.
* Highlights the given offsets in the input or output.
* We will only highlight if:
* - input hasn't changed since last bake
* - last bake was a full bake
* - all operations in the recipe support highlighting
*
* @param {Object} pos - The position object for the highlight.
* @param {number} pos.start - The start offset.
* @param {number} pos.end - The end offset.
* @param {string} io
* @param {ViewUpdate} e
*/
highlightOutput(pos) {
selectionChange(io, e) {
// Confirm we are not currently baking
if (!this.app.autoBake_ || this.app.baking) return false;
this.manager.worker.highlight(this.app.getRecipeConfig(), "forward", pos);
// Confirm this was a user-generated event to prevent looping
// from setting the selection in this class
if (!e.transactions[0].isUserEvent("select")) return false;
this.currentSelectionRanges = [];
// Confirm some non-empty ranges are set
const selectionRanges = e.state.selection.ranges;
// Adjust offsets based on the width of the character set
const inputCharacterWidth = chrEncWidth(this.manager.input.getChrEnc());
const outputCharacterWidth = chrEncWidth(this.manager.output.getChrEnc());
let ratio = 1;
if (inputCharacterWidth !== outputCharacterWidth &&
inputCharacterWidth !== 0 && outputCharacterWidth !== 0) {
ratio = io === "input" ?
inputCharacterWidth / outputCharacterWidth :
outputCharacterWidth / inputCharacterWidth;
}
// Loop through ranges and send request for output offsets for each one
const direction = io === "input" ? "forward" : "reverse";
for (const range of selectionRanges) {
const pos = [{
start: Math.floor(range.from * ratio),
end: Math.floor(range.to * ratio)
}];
this.manager.worker.highlight(this.app.getRecipeConfig(), direction, pos);
}
}
/**
* Highlights the given offsets in the input.
* We will only highlight if:
* - input hasn't changed since last bake
* - last bake was a full bake
* - all operations in the recipe support highlighting
*
* @param {Object} pos - The position object for the highlight.
* @param {number} pos.start - The start offset.
* @param {number} pos.end - The end offset.
*/
highlightInput(pos) {
if (!this.app.autoBake_ || this.app.baking) return false;
this.manager.worker.highlight(this.app.getRecipeConfig(), "reverse", pos);
}
/**
* Displays highlight offsets sent back from the Chef.
*
* @param {Object} pos - The position object for the highlight.
* @param {Object[]} pos - The position object for the highlight.
* @param {number} pos.start - The start offset.
* @param {number} pos.end - The end offset.
* @param {string} direction
*/
displayHighlights(pos, direction) {
if (!pos) return;
if (this.manager.tabs.getActiveInputTab() !== this.manager.tabs.getActiveOutputTab()) return;
if (this.manager.tabs.getActiveTab("input") !== this.manager.tabs.getActiveTab("output")) return;
const io = direction === "forward" ? "output" : "input";
document.getElementById(io + "-selection-info").innerHTML = this.selectionInfo(pos[0].start, pos[0].end);
this.highlight(
document.getElementById(io + "-text"),
document.getElementById(io + "-highlighter"),
pos);
this.highlight(io, pos);
}
/**
* Adds the relevant HTML to the specified highlight element such that highlighting appears
* underneath the correct offset.
* Sends selection updates to the relevant EditorView.
*
* @param {element} textarea - The input or output textarea.
* @param {element} highlighter - The input or output highlighter element.
* @param {Object} pos - The position object for the highlight.
* @param {number} pos.start - The start offset.
* @param {number} pos.end - The end offset.
* @param {string} io - The input or output
* @param {Object[]} ranges - An array of position objects to highlight
* @param {number} ranges.start - The start offset
* @param {number} ranges.end - The end offset
*/
async highlight(textarea, highlighter, pos) {
async highlight(io, ranges) {
if (!this.app.options.showHighlighter) return false;
if (!this.app.options.attemptHighlight) return false;
if (!ranges || !ranges.length) return false;
// Check if there is a carriage return in the output dish as this will not
// be displayed by the HTML textarea and will mess up highlighting offsets.
if (await this.manager.output.containsCR()) return false;
const view = io === "input" ?
this.manager.input.inputEditorView :
this.manager.output.outputEditorView;
const startPlaceholder = "[startHighlight]";
const startPlaceholderRegex = /\[startHighlight\]/g;
const endPlaceholder = "[endHighlight]";
const endPlaceholderRegex = /\[endHighlight\]/g;
let text = textarea.value;
// Add new SelectionRanges to existing ones
for (const range of ranges) {
if (typeof range.start !== "number" ||
typeof range.end !== "number")
continue;
const selection = range.end <= range.start ?
EditorSelection.cursor(range.start) :
EditorSelection.range(range.start, range.end);
// Put placeholders in position
// If there's only one value, select that
// If there are multiple, ignore the first one and select all others
if (pos.length === 1) {
if (pos[0].end < pos[0].start) return;
text = text.slice(0, pos[0].start) +
startPlaceholder + text.slice(pos[0].start, pos[0].end) + endPlaceholder +
text.slice(pos[0].end, text.length);
} else {
// O(n^2) - Can anyone improve this without overwriting placeholders?
let result = "",
endPlaced = true;
for (let i = 0; i < text.length; i++) {
for (let j = 1; j < pos.length; j++) {
if (pos[j].end < pos[j].start) continue;
if (pos[j].start === i) {
result += startPlaceholder;
endPlaced = false;
}
if (pos[j].end === i) {
result += endPlaceholder;
endPlaced = true;
}
}
result += text[i];
}
if (!endPlaced) result += endPlaceholder;
text = result;
this.currentSelectionRanges.push(selection);
}
const cssClass = "hl1";
// Remove HTML tags
text = text
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/\n/g, "&#10;")
// Convert placeholders to tags
.replace(startPlaceholderRegex, "<span class=\""+cssClass+"\">")
.replace(endPlaceholderRegex, "</span>") + "&nbsp;";
// Adjust width to allow for scrollbars
highlighter.style.width = textarea.clientWidth + "px";
highlighter.innerHTML = text;
highlighter.scrollTop = textarea.scrollTop;
highlighter.scrollLeft = textarea.scrollLeft;
// Set selection
if (this.currentSelectionRanges.length) {
try {
view.dispatch({
selection: EditorSelection.create(this.currentSelectionRanges),
scrollIntoView: true
});
} catch (err) {
// Ignore Range Errors
if (!err.toString().startsWith("RangeError")) {
log.error(err);
}
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -6,6 +6,7 @@
import HTMLOperation from "../HTMLOperation.mjs";
import Sortable from "sortablejs";
import {fuzzyMatch, calcMatchRanges} from "../../core/lib/FuzzyMatch.mjs";
/**
@ -108,28 +109,37 @@ class OperationsWaiter {
const matchedOps = [];
const matchedDescs = [];
const searchStr = inStr.toLowerCase();
// Create version with no whitespace for the fuzzy match
// Helps avoid missing matches e.g. query "TCP " would not find "Parse TCP"
const inStrNWS = inStr.replace(/\s/g, "");
for (const opName in this.app.operations) {
const op = this.app.operations[opName];
const namePos = opName.toLowerCase().indexOf(searchStr);
const descPos = op.description.toLowerCase().indexOf(searchStr);
if (namePos >= 0 || descPos >= 0) {
// Match op name using fuzzy match
const [nameMatch, score, idxs] = fuzzyMatch(inStrNWS, opName);
// Match description based on exact match
const descPos = op.description.toLowerCase().indexOf(inStr.toLowerCase());
if (nameMatch || descPos >= 0) {
const operation = new HTMLOperation(opName, this.app.operations[opName], this.app, this.manager);
if (highlight) {
operation.highlightSearchString(searchStr, namePos, descPos);
operation.highlightSearchStrings(calcMatchRanges(idxs), [[descPos, inStr.length]]);
}
if (namePos < 0) {
matchedOps.push(operation);
if (nameMatch) {
matchedOps.push([operation, score]);
} else {
matchedDescs.push(operation);
}
}
}
return matchedDescs.concat(matchedOps);
// Sort matched operations based on fuzzy score
matchedOps.sort((a, b) => b[1] - a[1]);
return matchedOps.map(a => a[0]).concat(matchedDescs);
}

View file

@ -26,33 +26,30 @@ class OptionsWaiter {
* @param {Object} options
*/
load(options) {
for (const option in options) {
this.app.options[option] = options[option];
}
Object.assign(this.app.options, options);
// Set options to match object
const cboxes = document.querySelectorAll("#options-body input[type=checkbox]");
let i;
for (i = 0; i < cboxes.length; i++) {
cboxes[i].checked = this.app.options[cboxes[i].getAttribute("option")];
}
document.querySelectorAll("#options-body input[type=checkbox]").forEach(cbox => {
cbox.checked = this.app.options[cbox.getAttribute("option")];
});
const nboxes = document.querySelectorAll("#options-body input[type=number]");
for (i = 0; i < nboxes.length; i++) {
nboxes[i].value = this.app.options[nboxes[i].getAttribute("option")];
nboxes[i].dispatchEvent(new CustomEvent("change", {bubbles: true}));
}
document.querySelectorAll("#options-body input[type=number]").forEach(nbox => {
nbox.value = this.app.options[nbox.getAttribute("option")];
nbox.dispatchEvent(new CustomEvent("change", {bubbles: true}));
});
const selects = document.querySelectorAll("#options-body select");
for (i = 0; i < selects.length; i++) {
const val = this.app.options[selects[i].getAttribute("option")];
document.querySelectorAll("#options-body select").forEach(select => {
const val = this.app.options[select.getAttribute("option")];
if (val) {
selects[i].value = val;
selects[i].dispatchEvent(new CustomEvent("change", {bubbles: true}));
select.value = val;
select.dispatchEvent(new CustomEvent("change", {bubbles: true}));
} else {
selects[i].selectedIndex = 0;
select.selectedIndex = 0;
}
}
});
// Initialise options
this.setWordWrap();
}
@ -136,19 +133,8 @@ class OptionsWaiter {
* Sets or unsets word wrap on the input and output depending on the wordWrap option value.
*/
setWordWrap() {
document.getElementById("input-text").classList.remove("word-wrap");
document.getElementById("output-text").classList.remove("word-wrap");
document.getElementById("output-html").classList.remove("word-wrap");
document.getElementById("input-highlighter").classList.remove("word-wrap");
document.getElementById("output-highlighter").classList.remove("word-wrap");
if (!this.app.options.wordWrap) {
document.getElementById("input-text").classList.add("word-wrap");
document.getElementById("output-text").classList.add("word-wrap");
document.getElementById("output-html").classList.add("word-wrap");
document.getElementById("input-highlighter").classList.add("word-wrap");
document.getElementById("output-highlighter").classList.add("word-wrap");
}
this.manager.input.setWordWrap(this.app.options.wordWrap);
this.manager.output.setWordWrap(this.app.options.wordWrap);
}
@ -159,7 +145,6 @@ class OptionsWaiter {
*/
themeChange(e) {
const themeClass = e.target.value;
this.changeTheme(themeClass);
}
@ -188,6 +173,8 @@ class OptionsWaiter {
log.setLevel(level, false);
this.manager.worker.setLogLevel();
this.manager.input.setLogLevel();
this.manager.output.setLogLevel();
this.manager.background.setLogLevel();
}
}

File diff suppressed because it is too large Load diff

View file

@ -7,6 +7,7 @@
import HTMLOperation from "../HTMLOperation.mjs";
import Sortable from "sortablejs";
import Utils from "../../core/Utils.mjs";
import {escapeControlChars} from "../utils/editorUtils.mjs";
/**
@ -132,7 +133,7 @@ class RecipeWaiter {
// Reinitialise the popover on the original element in the ops list because for some reason it
// gets destroyed and recreated. If the clone isn't in the ops list, we use the original item instead.
let enableOpsElement;
if (evt.clone.parentNode && evt.clone.parentNode.classList.contains("op-list")) {
if (evt.clone?.parentNode?.classList?.contains("op-list")) {
enableOpsElement = evt.clone;
} else {
enableOpsElement = evt.item;
@ -162,13 +163,13 @@ class RecipeWaiter {
e.stopPropagation();
e.preventDefault();
if (e.target.className && e.target.className.indexOf("category-title") > -1) {
if (e.target?.className?.indexOf("category-title") > -1) {
// Hovering over the a
e.target.classList.add("favourites-hover");
} else if (e.target.parentNode.className && e.target.parentNode.className.indexOf("category-title") > -1) {
} else if (e.target?.parentNode?.className?.indexOf("category-title") > -1) {
// Hovering over the Edit button
e.target.parentNode.classList.add("favourites-hover");
} else if (e.target.parentNode.parentNode.className && e.target.parentNode.parentNode.className.indexOf("category-title") > -1) {
} else if (e.target?.parentNode?.parentNode?.className?.indexOf("category-title") > -1) {
// Hovering over the image on the Edit button
e.target.parentNode.parentNode.classList.add("favourites-hover");
}
@ -210,7 +211,7 @@ class RecipeWaiter {
* @fires Manager#statechange
*/
ingChange(e) {
if (e && e.target && e.target.classList.contains("no-state-change")) return;
if (e && e?.target?.classList?.contains("no-state-change")) return;
window.dispatchEvent(this.manager.statechange);
}
@ -357,7 +358,7 @@ class RecipeWaiter {
};
} else if (ingList[j].getAttribute("type") === "number") {
// number
ingredients[j] = parseFloat(ingList[j].value, 10);
ingredients[j] = parseFloat(ingList[j].value);
} else {
// all others
ingredients[j] = ingList[j].value;
@ -609,7 +610,7 @@ class RecipeWaiter {
const registerList = [];
for (let i = 0; i < registers.length; i++) {
registerList.push(`$R${numPrevRegisters + i} = ${Utils.escapeHtml(Utils.truncate(Utils.printable(registers[i]), 100))}`);
registerList.push(`$R${numPrevRegisters + i} = ${escapeControlChars(Utils.escapeHtml(Utils.truncate(registers[i], 100)))}`);
}
const registerListEl = `<div class="register-list">
${registerList.join("<br>")}
@ -625,42 +626,6 @@ class RecipeWaiter {
adjustWidth() {
const recList = document.getElementById("rec-list");
if (!this.ingredientRuleID) {
this.ingredientRuleID = null;
this.ingredientChildRuleID = null;
// Find relevant rules in the stylesheet
// try/catch for chrome 64+ CORS error on cssRules.
try {
for (const i in document.styleSheets[0].cssRules) {
if (document.styleSheets[0].cssRules[i].selectorText === ".ingredients") {
this.ingredientRuleID = i;
}
if (document.styleSheets[0].cssRules[i].selectorText === ".ingredients > div") {
this.ingredientChildRuleID = i;
}
}
} catch (e) {
// Do nothing.
}
}
if (!this.ingredientRuleID || !this.ingredientChildRuleID) return;
const ingredientRule = document.styleSheets[0].cssRules[this.ingredientRuleID];
const ingredientChildRule = document.styleSheets[0].cssRules[this.ingredientChildRuleID];
if (recList.clientWidth < 450) {
ingredientRule.style.gridTemplateColumns = "auto auto";
ingredientChildRule.style.gridColumn = "1 / span 2";
} else if (recList.clientWidth < 620) {
ingredientRule.style.gridTemplateColumns = "auto auto auto";
ingredientChildRule.style.gridColumn = "1 / span 3";
} else {
ingredientRule.style.gridTemplateColumns = "auto auto auto auto";
ingredientChildRule.style.gridColumn = "1 / span 4";
}
// Hide Chef icon on Bake button if the page is compressed
const bakeIcon = document.querySelector("#bake img");
@ -676,7 +641,7 @@ class RecipeWaiter {
const controlsContent = document.getElementById("controls-content");
const scale = (controls.clientWidth - 1) / controlsContent.scrollWidth;
controlsContent.style.transform = `translate(-50%, -50%) scale(${scale})`;
controlsContent.style.transform = `scale(${scale})`;
}
}

View file

@ -30,8 +30,7 @@ class SeasonalWaiter {
window.addEventListener("keydown", this.konamiCodeListener.bind(this));
// CyberChef Challenge
// eslint-disable-next-line no-console
console.log("43 6f 6e 67 72 61 74 75 6c 61 74 69 6f 6e 73 2c 20 79 6f 75 20 68 61 76 65 20 63 6f 6d 70 6c 65 74 65 64 20 43 79 62 65 72 43 68 65 66 20 63 68 61 6c 6c 65 6e 67 65 20 23 31 21 0a 0a 54 68 69 73 20 63 68 61 6c 6c 65 6e 67 65 20 65 78 70 6c 6f 72 65 64 20 68 65 78 61 64 65 63 69 6d 61 6c 20 65 6e 63 6f 64 69 6e 67 2e 20 54 6f 20 6c 65 61 72 6e 20 6d 6f 72 65 2c 20 76 69 73 69 74 20 77 69 6b 69 70 65 64 69 61 2e 6f 72 67 2f 77 69 6b 69 2f 48 65 78 61 64 65 63 69 6d 61 6c 2e 0a 0a 54 68 65 20 63 6f 64 65 20 66 6f 72 20 74 68 69 73 20 63 68 61 6c 6c 65 6e 67 65 20 69 73 20 39 64 34 63 62 63 65 66 2d 62 65 35 32 2d 34 37 35 31 2d 61 32 62 32 2d 38 33 33 38 65 36 34 30 39 34 31 36 20 28 6b 65 65 70 20 74 68 69 73 20 70 72 69 76 61 74 65 29 2e 0a 0a 54 68 65 20 6e 65 78 74 20 63 68 61 6c 6c 65 6e 67 65 20 63 61 6e 20 62 65 20 66 6f 75 6e 64 20 61 74 20 68 74 74 70 73 3a 2f 2f 70 61 73 74 65 62 69 6e 2e 63 6f 6d 2f 47 53 6e 54 41 6d 6b 56 2e");
log.info("43 6f 6e 67 72 61 74 75 6c 61 74 69 6f 6e 73 2c 20 79 6f 75 20 68 61 76 65 20 63 6f 6d 70 6c 65 74 65 64 20 43 79 62 65 72 43 68 65 66 20 63 68 61 6c 6c 65 6e 67 65 20 23 31 21 0a 0a 54 68 69 73 20 63 68 61 6c 6c 65 6e 67 65 20 65 78 70 6c 6f 72 65 64 20 68 65 78 61 64 65 63 69 6d 61 6c 20 65 6e 63 6f 64 69 6e 67 2e 20 54 6f 20 6c 65 61 72 6e 20 6d 6f 72 65 2c 20 76 69 73 69 74 20 77 69 6b 69 70 65 64 69 61 2e 6f 72 67 2f 77 69 6b 69 2f 48 65 78 61 64 65 63 69 6d 61 6c 2e 0a 0a 54 68 65 20 63 6f 64 65 20 66 6f 72 20 74 68 69 73 20 63 68 61 6c 6c 65 6e 67 65 20 69 73 20 39 64 34 63 62 63 65 66 2d 62 65 35 32 2d 34 37 35 31 2d 61 32 62 32 2d 38 33 33 38 65 36 34 30 39 34 31 36 20 28 6b 65 65 70 20 74 68 69 73 20 70 72 69 76 61 74 65 29 2e 0a 0a 54 68 65 20 6e 65 78 74 20 63 68 61 6c 6c 65 6e 67 65 20 63 61 6e 20 62 65 20 66 6f 75 6e 64 20 61 74 20 68 74 74 70 73 3a 2f 2f 70 61 73 74 65 62 69 6e 2e 63 6f 6d 2f 47 53 6e 54 41 6d 6b 56 2e");
}

View file

@ -48,24 +48,6 @@ class TabWaiter {
return -1;
}
/**
* Gets the currently active input tab number
*
* @returns {number}
*/
getActiveInputTab() {
return this.getActiveTab("input");
}
/**
* Gets the currently active output tab number
*
* @returns {number}
*/
getActiveOutputTab() {
return this.getActiveTab("output");
}
/**
* Gets the li element for the tab of a given input number
*
@ -83,26 +65,6 @@ class TabWaiter {
return null;
}
/**
* Gets the li element for an input tab of the given input number
*
* @param {inputNum} - The inputNum of the tab we're trying to get
* @returns {Element}
*/
getInputTabItem(inputNum) {
return this.getTabItem(inputNum, "input");
}
/**
* Gets the li element for an output tab of the given input number
*
* @param {number} inputNum
* @returns {Element}
*/
getOutputTabItem(inputNum) {
return this.getTabItem(inputNum, "output");
}
/**
* Gets a list of tab numbers for the currently displayed tabs
*
@ -120,24 +82,6 @@ class TabWaiter {
return nums;
}
/**
* Gets a list of tab numbers for the currently displayed input tabs
*
* @returns {number[]}
*/
getInputTabList() {
return this.getTabList("input");
}
/**
* Gets a list of tab numbers for the currently displayed output tabs
*
* @returns {number[]}
*/
getOutputTabList() {
return this.getTabList("output");
}
/**
* Creates a new tab element for the tab bar
*
@ -154,11 +98,8 @@ class TabWaiter {
const newTabContent = document.createElement("div");
newTabContent.classList.add(`${io}-tab-content`);
newTabContent.innerText = `Tab ${inputNum.toString()}`;
newTabContent.addEventListener("wheel", this.manager[io].scrollTab.bind(this.manager[io]), {passive: false});
newTab.appendChild(newTabContent);
if (io === "input") {
@ -166,52 +107,24 @@ class TabWaiter {
newTabButtonIcon = document.createElement("i");
newTabButton.type = "button";
newTabButton.className = "btn btn-primary bmd-btn-icon btn-close-tab";
newTabButtonIcon.classList.add("material-icons");
newTabButtonIcon.innerText = "clear";
newTabButton.appendChild(newTabButtonIcon);
newTabButton.addEventListener("click", this.manager.input.removeTabClick.bind(this.manager.input));
newTab.appendChild(newTabButton);
}
return newTab;
}
/**
* Creates a new tab element for the input tab bar
*
* @param {number} inputNum - The inputNum of the new input tab
* @param {boolean} [active=false] - If true, sets the tab to active
* @returns {Element}
*/
createInputTabElement(inputNum, active=false) {
return this.createTabElement(inputNum, active, "input");
}
/**
* Creates a new tab element for the output tab bar
*
* @param {number} inputNum - The inputNum of the new output tab
* @param {boolean} [active=false] - If true, sets the tab to active
* @returns {Element}
*/
createOutputTabElement(inputNum, active=false) {
return this.createTabElement(inputNum, active, "output");
}
/**
* Displays the tab bar for both the input and output
*/
showTabBar() {
document.getElementById("input-tabs-wrapper").style.display = "block";
document.getElementById("output-tabs-wrapper").style.display = "block";
document.getElementById("input-wrapper").classList.add("show-tabs");
document.getElementById("output-wrapper").classList.add("show-tabs");
document.getElementById("save-all-to-file").style.display = "inline-block";
}
@ -221,10 +134,8 @@ class TabWaiter {
hideTabBar() {
document.getElementById("input-tabs-wrapper").style.display = "none";
document.getElementById("output-tabs-wrapper").style.display = "none";
document.getElementById("input-wrapper").classList.remove("show-tabs");
document.getElementById("output-wrapper").classList.remove("show-tabs");
document.getElementById("save-all-to-file").style.display = "none";
}
@ -271,30 +182,6 @@ class TabWaiter {
}
}
/**
* Refreshes the input tabs, and changes to activeTab
*
* @param {number[]} nums - The inputNums to be displayed as tabs
* @param {number} activeTab - The tab to change to
* @param {boolean} tabsLeft - True if there are input tabs to the left of the displayed tabs
* @param {boolean} tabsRight - True if there are input tabs to the right of the displayed tabs
*/
refreshInputTabs(nums, activeTab, tabsLeft, tabsRight) {
this.refreshTabs(nums, activeTab, tabsLeft, tabsRight, "input");
}
/**
* Refreshes the output tabs, and changes to activeTab
*
* @param {number[]} nums - The inputNums to be displayed as tabs
* @param {number} activeTab - The tab to change to
* @param {boolean} tabsLeft - True if there are output tabs to the left of the displayed tabs
* @param {boolean} tabsRight - True if there are output tabs to the right of the displayed tabs
*/
refreshOutputTabs(nums, activeTab, tabsLeft, tabsRight) {
this.refreshTabs(nums, activeTab, tabsLeft, tabsRight, "output");
}
/**
* Changes the active tab to a different tab
*
@ -305,9 +192,6 @@ class TabWaiter {
changeTab(inputNum, io) {
const tabsList = document.getElementById(`${io}-tabs`);
this.manager.highlighter.removeHighlights();
getSelection().removeAllRanges();
let found = false;
for (let i = 0; i < tabsList.children.length; i++) {
const tabNum = parseInt(tabsList.children.item(i).getAttribute("inputNum"), 10);
@ -322,26 +206,6 @@ class TabWaiter {
return found;
}
/**
* Changes the active input tab to a different tab
*
* @param {number} inputNum
* @returns {boolean} - False if the tab is not currently being displayed
*/
changeInputTab(inputNum) {
return this.changeTab(inputNum, "input");
}
/**
* Changes the active output tab to a different tab
*
* @param {number} inputNum
* @returns {boolean} - False if the tab is not currently being displayed
*/
changeOutputTab(inputNum) {
return this.changeTab(inputNum, "output");
}
/**
* Updates the tab header to display a preview of the tab contents
*
@ -361,26 +225,6 @@ class TabWaiter {
tab.firstElementChild.innerText = headerData;
}
/**
* Updates the input tab header to display a preview of the tab contents
*
* @param {number} inputNum - The inputNum of the tab to update the header of
* @param {string} data - The data to display in the tab header
*/
updateInputTabHeader(inputNum, data) {
this.updateTabHeader(inputNum, data, "input");
}
/**
* Updates the output tab header to display a preview of the tab contents
*
* @param {number} inputNum - The inputNum of the tab to update the header of
* @param {string} data - The data to display in the tab header
*/
updateOutputTabHeader(inputNum, data) {
this.updateTabHeader(inputNum, data, "output");
}
/**
* Updates the tab background to display the progress of the current tab
*
@ -401,28 +245,6 @@ class TabWaiter {
}
}
/**
* Updates the input tab background to display its progress
*
* @param {number} inputNum
* @param {number} progress
* @param {number} total
*/
updateInputTabProgress(inputNum, progress, total) {
this.updateTabProgress(inputNum, progress, total, "input");
}
/**
* Updates the output tab background to display its progress
*
* @param {number} inputNum
* @param {number} progress
* @param {number} total
*/
updateOutputTabProgress(inputNum, progress, total) {
this.updateTabProgress(inputNum, progress, total, "output");
}
}
export default TabWaiter;

View file

@ -0,0 +1,182 @@
/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2023
* @license Apache-2.0
*/
/**
* Waiter to handle timing of the baking process.
*/
class TimingWaiter {
/**
* TimingWaiter constructor.
*
* @param {App} app - The main view object for CyberChef.
* @param {Manager} manager - The CyberChef event manager.
*/
constructor(app, manager) {
this.app = app;
this.manager = manager;
this.inputs = {};
/*
Inputs example:
"1": {
"inputEncodingStart": 0,
"inputEncodingEnd": 0,
"trigger": 0
"chefWorkerTasked": 0,
"bakeComplete": 0,
"bakeDuration": 0,
"settingOutput": 0,
"outputDecodingStart": 0,
"outputDecodingEnd": 0,
"complete": 0
}
*/
}
/**
* Record the time for an input
*
* @param {string} event
* @param {number} inputNum
* @param {number} value
*/
recordTime(event, inputNum, value=Date.now()) {
inputNum = inputNum.toString();
if (!Object.keys(this.inputs).includes(inputNum)) {
this.inputs[inputNum] = {};
}
log.debug(`Recording ${event} for input ${inputNum}`);
this.inputs[inputNum][event] = value;
}
/**
* The duration of the main stages of a bake
*
* @param {number} inputNum
* @returns {number}
*/
duration(inputNum) {
const input = this.inputs[inputNum.toString()];
// If this input has not been encoded yet, we cannot calculate a time
if (!input ||
!input.trigger ||
!input.inputEncodingEnd ||
!input.inputEncodingStart)
return 0;
// input encoding can happen before a bake is triggered, so it is calculated separately
const inputEncodingTotal = input.inputEncodingEnd - input.inputEncodingStart;
let total = 0, outputDecodingTotal = 0;
if (input.bakeComplete && input.bakeComplete > input.trigger)
total = input.bakeComplete - input.trigger;
if (input.settingOutput && input.settingOutput > input.trigger)
total = input.settingOutput - input.trigger;
if (input.outputDecodingStart && (input.outputDecodingStart > input.trigger) &&
input.outputDecodingEnd && (input.outputDecodingEnd > input.trigger)) {
total = input.outputDecodingEnd - input.trigger;
outputDecodingTotal = input.outputDecodingEnd - input.outputDecodingStart;
}
if (input.complete && input.complete > input.trigger)
total = inputEncodingTotal + input.bakeDuration + outputDecodingTotal;
return total;
}
/**
* The total time for a completed bake
*
* @param {number} inputNum
* @returns {number}
*/
overallDuration(inputNum) {
const input = this.inputs[inputNum.toString()];
// If this input has not been encoded yet, we cannot calculate a time
if (!input ||
!input.trigger ||
!input.inputEncodingEnd ||
!input.inputEncodingStart)
return 0;
// input encoding can happen before a bake is triggered, so it is calculated separately
const inputEncodingTotal = input.inputEncodingEnd - input.inputEncodingStart;
let total = 0;
if (input.bakeComplete && input.bakeComplete > input.trigger)
total = input.bakeComplete - input.trigger;
if (input.settingOutput && input.settingOutput > input.trigger)
total = input.settingOutput - input.trigger;
if (input.outputDecodingStart && input.outputDecodingStart > input.trigger)
total = input.outputDecodingStart - input.trigger;
if (input.outputDecodingEnd && input.outputDecodingEnd > input.trigger)
total = input.outputDecodingEnd - input.trigger;
if (input.complete && input.complete > input.trigger)
total = input.complete - input.trigger;
return total + inputEncodingTotal;
}
/**
* Prints out the time between stages
*
* @param {number} inputNum
* @returns {string}
*/
printStages(inputNum) {
const input = this.inputs[inputNum.toString()];
if (!input || !input.trigger) return "";
const total = this.overallDuration(inputNum),
inputEncoding = input.inputEncodingEnd - input.inputEncodingStart,
outputDecoding = input.outputDecodingEnd - input.outputDecodingStart,
overhead = total - inputEncoding - outputDecoding - input.bakeDuration;
return `Input encoding: ${inputEncoding}ms
Recipe duration: ${input.bakeDuration}ms
Output decoding: ${outputDecoding}ms
<span class="small">Threading overhead: ${overhead}ms</span>`;
}
/**
* Logs every interval
*
* @param {number} inputNum
*/
logAllTimes(inputNum) {
const input = this.inputs[inputNum.toString()];
if (!input || !input.trigger) return;
try {
log.debug(`Trigger: ${input.trigger}
inputEncodingStart: ${input.inputEncodingStart} | ${input.inputEncodingStart - input.trigger}ms since trigger
inputEncodingEnd: ${input.inputEncodingEnd} | ${input.inputEncodingEnd - input.inputEncodingStart}ms input encoding time
chefWorkerTasked: ${input.chefWorkerTasked} | ${input.chefWorkerTasked - input.trigger}ms since trigger
bakeDuration: | ${input.bakeDuration}ms duration in worker
bakeComplete: ${input.bakeComplete} | ${input.bakeComplete - input.chefWorkerTasked}ms since worker tasked
settingOutput: ${input.settingOutput} | ${input.settingOutput - input.bakeComplete}ms since worker finished
outputDecodingStart: ${input.outputDecodingStart} | ${input.outputDecodingStart - input.settingOutput}ms since output set
outputDecodingEnd: ${input.outputDecodingEnd} | ${input.outputDecodingEnd - input.outputDecodingStart}ms output encoding time
complete: ${input.complete} | ${input.complete - input.outputDecodingEnd}ms since output decoded
Total: | ${input.complete - input.trigger}ms since trigger`);
} catch (err) {}
}
}
export default TimingWaiter;

View file

@ -23,11 +23,11 @@ class WindowWaiter {
/**
* Handler for window resize events.
* Resets the layout of CyberChef's panes after 200ms (so that continuous resizing doesn't cause
* Resets adjustable component sizes after 200ms (so that continuous resizing doesn't cause
* continuous resetting).
*/
windowResize() {
debounce(this.app.resetLayout, 200, "windowResize", this.app, [])();
debounce(this.app.adjustComponentSizes, 200, "windowResize", this.app, [])();
}

View file

@ -5,8 +5,8 @@
* @license Apache-2.0
*/
import ChefWorker from "worker-loader?inline&fallback=false!../../core/ChefWorker.js";
import DishWorker from "worker-loader?inline&fallback=false!../workers/DishWorker.mjs";
import ChefWorker from "worker-loader?inline=no-fallback!../../core/ChefWorker.js";
import DishWorker from "worker-loader?inline=no-fallback!../workers/DishWorker.mjs";
import { debounce } from "../../core/Utils.mjs";
/**
@ -72,6 +72,10 @@ class WorkerWaiter {
this.dishWorker.worker = new DishWorker();
this.dishWorker.worker.addEventListener("message", this.handleDishMessage.bind(this));
this.dishWorker.worker.postMessage({
action: "setLogLevel",
data: log.getLevel()
});
if (this.dishWorkerQueue.length > 0) {
this.postDishMessage(this.dishWorkerQueue.splice(0, 1)[0]);
@ -89,22 +93,27 @@ class WorkerWaiter {
return -1;
}
log.debug("Adding new ChefWorker");
log.debug(`Adding new ChefWorker (${this.chefWorkers.length + 1}/${this.maxWorkers})`);
// Create a new ChefWorker and send it the docURL
const newWorker = new ChefWorker();
newWorker.addEventListener("message", this.handleChefMessage.bind(this));
newWorker.postMessage({
action: "setLogPrefix",
data: "ChefWorker"
});
newWorker.postMessage({
action: "setLogLevel",
data: log.getLevel()
});
let docURL = document.location.href.split(/[#?]/)[0];
const index = docURL.lastIndexOf("/");
if (index > 0) {
docURL = docURL.substring(0, index);
}
newWorker.postMessage({"action": "docURL", "data": docURL});
newWorker.postMessage({
action: "setLogLevel",
data: log.getLevel()
});
// Store the worker, whether or not it's active, and the inputNum as an object
const newWorkerObj = {
@ -177,7 +186,7 @@ class WorkerWaiter {
handleChefMessage(e) {
const r = e.data;
let inputNum = 0;
log.debug(`Receiving ${r.action} from ChefWorker.`);
log.debug(`Receiving '${r.action}' from ChefWorker.`);
if (Object.prototype.hasOwnProperty.call(r.data, "inputNum")) {
inputNum = r.data.inputNum;
@ -188,6 +197,8 @@ class WorkerWaiter {
switch (r.action) {
case "bakeComplete":
log.debug(`Bake ${inputNum} complete.`);
this.manager.timing.recordTime("bakeComplete", inputNum);
this.manager.timing.recordTime("bakeDuration", inputNum, r.data.duration);
if (r.data.error) {
this.app.handleError(r.data.error);
@ -217,7 +228,7 @@ class WorkerWaiter {
break;
case "workerLoaded":
this.app.workerLoaded = true;
log.debug("ChefWorker loaded.");
log.debug("ChefWorker loaded");
if (!this.loaded) {
this.app.loaded();
this.loaded = true;
@ -266,7 +277,7 @@ class WorkerWaiter {
if (progress !== false) {
this.manager.output.updateOutputStatus("error", inputNum);
if (inputNum === this.manager.tabs.getActiveInputTab()) {
if (inputNum === this.manager.tabs.getActiveTab("input")) {
this.manager.recipe.updateBreakpointIndicator(progress);
}
@ -315,36 +326,42 @@ class WorkerWaiter {
* Cancels the current bake by terminating and removing all ChefWorkers
*
* @param {boolean} [silent=false] - If true, don't set the output
* @param {boolean} killAll - If true, kills all chefWorkers regardless of status
* @param {boolean} [killAll=false] - If true, kills all chefWorkers regardless of status
*/
cancelBake(silent, killAll) {
cancelBake(silent=false, killAll=false) {
const deactiveOutputs = new Set();
for (let i = this.chefWorkers.length - 1; i >= 0; i--) {
if (this.chefWorkers[i].active || killAll) {
const inputNum = this.chefWorkers[i].inputNum;
this.removeChefWorker(this.chefWorkers[i]);
this.manager.output.updateOutputStatus("inactive", inputNum);
deactiveOutputs.add(inputNum);
}
}
this.setBakingStatus(false);
for (let i = 0; i < this.inputs.length; i++) {
this.manager.output.updateOutputStatus("inactive", this.inputs[i].inputNum);
}
this.inputs.forEach(input => {
deactiveOutputs.add(input.inputNum);
});
for (let i = 0; i < this.inputNums.length; i++) {
this.manager.output.updateOutputStatus("inactive", this.inputNums[i]);
}
this.inputNums.forEach(inputNum => {
deactiveOutputs.add(inputNum);
});
const tabList = this.manager.tabs.getOutputTabList();
for (let i = 0; i < tabList.length; i++) {
this.manager.tabs.getOutputTabItem(tabList[i]).style.background = "";
}
deactiveOutputs.forEach(num => {
this.manager.output.updateOutputStatus("inactive", num);
});
const tabList = this.manager.tabs.getTabList("output");
tabList.forEach(tab => {
this.manager.tabs.getTabItem(tab, "output").style.background = "";
});
this.inputs = [];
this.inputNums = [];
this.totalOutputs = 0;
this.loadingOutputs = 0;
if (!silent) this.manager.output.set(this.manager.tabs.getActiveOutputTab());
if (!silent) this.manager.output.set(this.manager.tabs.getActiveTab("output"));
}
/**
@ -455,6 +472,7 @@ class WorkerWaiter {
if (input instanceof ArrayBuffer || ArrayBuffer.isView(input)) {
transferable = [input];
}
this.manager.timing.recordTime("chefWorkerTasked", nextInput.inputNum);
this.chefWorkers[workerIdx].worker.postMessage({
action: "bake",
data: {
@ -550,10 +568,12 @@ class WorkerWaiter {
* @param {boolean} inputData.step - If true, only execute the next operation in the recipe
* @param {number} inputData.progress - The current progress through the recipe. Used when stepping
*/
async bakeAllInputs(inputData) {
async bakeInputs(inputData) {
log.debug(`Baking input list [${inputData.nums.join(",")}]`);
return await new Promise(resolve => {
if (this.app.baking) return;
const inputNums = inputData.nums;
const inputNums = inputData.nums.filter(n => n > 0);
const step = inputData.step;
// Use cancelBake to clear out the inputs
@ -586,6 +606,7 @@ class WorkerWaiter {
numBakes = this.inputNums.length;
}
for (let i = 0; i < numBakes; i++) {
this.manager.timing.recordTime("trigger", this.inputNums[0]);
this.manager.input.inputWorker.postMessage({
action: "bakeNext",
data: {
@ -595,6 +616,7 @@ class WorkerWaiter {
});
this.loadingOutputs++;
}
if (numBakes === 0) this.bakingComplete();
});
}
@ -626,7 +648,7 @@ class WorkerWaiter {
*/
handleDishMessage(e) {
const r = e.data;
log.debug(`Receiving ${r.action} from DishWorker`);
log.debug(`Receiving '${r.action}' from DishWorker`);
switch (r.action) {
case "dishReturned":
@ -645,7 +667,7 @@ class WorkerWaiter {
}
/**
* Asks the ChefWorker to return the dish as the specified type
* Asks the DishWorker to return the dish as the specified type
*
* @param {Dish} dish
* @param {string} type
@ -653,10 +675,9 @@ class WorkerWaiter {
*/
getDishAs(dish, type, callback) {
const id = this.callbackID++;
this.callbacks[id] = callback;
if (this.dishWorker.worker === null) this.setupDishWorker();
this.postDishMessage({
action: "getDishAs",
data: {
@ -668,7 +689,7 @@ class WorkerWaiter {
}
/**
* Asks the ChefWorker to get the title of the dish
* Asks the DishWorker to get the title of the dish
*
* @param {Dish} dish
* @param {number} maxLength
@ -677,9 +698,7 @@ class WorkerWaiter {
*/
getDishTitle(dish, maxLength, callback) {
const id = this.callbackID++;
this.callbacks[id] = callback;
if (this.dishWorker.worker === null) this.setupDishWorker();
this.postDishMessage({
@ -692,6 +711,29 @@ class WorkerWaiter {
});
}
/**
* Asks the DishWorker to translate a buffer into a specific character encoding
*
* @param {ArrayBuffer} buffer
* @param {number} encoding
* @param {Function} callback
* @returns {string}
*/
bufferToStr(buffer, encoding, callback) {
const id = this.callbackID++;
this.callbacks[id] = callback;
if (this.dishWorker.worker === null) this.setupDishWorker();
this.postDishMessage({
action: "bufferToStr",
data: {
buffer: buffer,
encoding: encoding,
id: id
}
});
}
/**
* Queues a message to be sent to the dishWorker
*
@ -729,12 +771,18 @@ class WorkerWaiter {
* Sets the console log level in the workers.
*/
setLogLevel() {
for (let i = 0; i < this.chefWorkers.length; i++) {
this.chefWorkers[i].worker.postMessage({
this.chefWorkers.forEach(cw => {
cw.worker.postMessage({
action: "setLogLevel",
data: log.getLevel()
});
}
});
if (!this.dishWorker.worker) return;
this.dishWorker.worker.postMessage({
action: "setLogLevel",
data: log.getLevel()
});
}
/**
@ -794,7 +842,7 @@ class WorkerWaiter {
*
* @param {Object[]} recipeConfig
* @param {string} direction
* @param {Object} pos - The position object for the highlight.
* @param {Object[]} pos - The position object for the highlight.
* @param {number} pos.start - The start offset.
* @param {number} pos.end - The end offset.
*/