123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359 |
- docReady(() => {
- if (!EVALEX_TRUSTED) {
- initPinBox();
- }
- // if we are in console mode, show the console.
- if (CONSOLE_MODE && EVALEX) {
- createInteractiveConsole();
- }
- const frames = document.querySelectorAll("div.traceback div.frame");
- if (EVALEX) {
- addConsoleIconToFrames(frames);
- }
- addEventListenersToElements(document.querySelectorAll("div.detail"), "click", () =>
- document.querySelector("div.traceback").scrollIntoView(false)
- );
- addToggleFrameTraceback(frames);
- addToggleTraceTypesOnClick(document.querySelectorAll("h2.traceback"));
- addInfoPrompt(document.querySelectorAll("span.nojavascript"));
- wrapPlainTraceback();
- });
- function addToggleFrameTraceback(frames) {
- frames.forEach((frame) => {
- frame.addEventListener("click", () => {
- frame.getElementsByTagName("pre")[0].parentElement.classList.toggle("expanded");
- });
- })
- }
- function wrapPlainTraceback() {
- const plainTraceback = document.querySelector("div.plain textarea");
- const wrapper = document.createElement("pre");
- const textNode = document.createTextNode(plainTraceback.textContent);
- wrapper.appendChild(textNode);
- plainTraceback.replaceWith(wrapper);
- }
- function initPinBox() {
- document.querySelector(".pin-prompt form").addEventListener(
- "submit",
- function (event) {
- event.preventDefault();
- const pin = encodeURIComponent(this.pin.value);
- const encodedSecret = encodeURIComponent(SECRET);
- const btn = this.btn;
- btn.disabled = true;
- fetch(
- `${document.location.pathname}?__debugger__=yes&cmd=pinauth&pin=${pin}&s=${encodedSecret}`
- )
- .then((res) => res.json())
- .then(({auth, exhausted}) => {
- if (auth) {
- EVALEX_TRUSTED = true;
- fadeOut(document.getElementsByClassName("pin-prompt")[0]);
- } else {
- alert(
- `Error: ${
- exhausted
- ? "too many attempts. Restart server to retry."
- : "incorrect pin"
- }`
- );
- }
- })
- .catch((err) => {
- alert("Error: Could not verify PIN. Network error?");
- console.error(err);
- })
- .finally(() => (btn.disabled = false));
- },
- false
- );
- }
- function promptForPin() {
- if (!EVALEX_TRUSTED) {
- const encodedSecret = encodeURIComponent(SECRET);
- fetch(
- `${document.location.pathname}?__debugger__=yes&cmd=printpin&s=${encodedSecret}`
- );
- const pinPrompt = document.getElementsByClassName("pin-prompt")[0];
- fadeIn(pinPrompt);
- document.querySelector('.pin-prompt input[name="pin"]').focus();
- }
- }
- /**
- * Helper function for shell initialization
- */
- function openShell(consoleNode, target, frameID) {
- promptForPin();
- if (consoleNode) {
- slideToggle(consoleNode);
- return consoleNode;
- }
- let historyPos = 0;
- const history = [""];
- const consoleElement = createConsole();
- const output = createConsoleOutput();
- const form = createConsoleInputForm();
- const command = createConsoleInput();
- target.parentNode.appendChild(consoleElement);
- consoleElement.append(output);
- consoleElement.append(form);
- form.append(command);
- command.focus();
- slideToggle(consoleElement);
- form.addEventListener("submit", (e) => {
- handleConsoleSubmit(e, command, frameID).then((consoleOutput) => {
- output.append(consoleOutput);
- command.focus();
- consoleElement.scrollTo(0, consoleElement.scrollHeight);
- const old = history.pop();
- history.push(command.value);
- if (typeof old !== "undefined") {
- history.push(old);
- }
- historyPos = history.length - 1;
- command.value = "";
- });
- });
- command.addEventListener("keydown", (e) => {
- if (e.key === "l" && e.ctrlKey) {
- output.innerText = "--- screen cleared ---";
- } else if (e.key === "ArrowUp" || e.key === "ArrowDown") {
- // Handle up arrow and down arrow.
- if (e.key === "ArrowUp" && historyPos > 0) {
- e.preventDefault();
- historyPos--;
- } else if (e.key === "ArrowDown" && historyPos < history.length - 1) {
- historyPos++;
- }
- command.value = history[historyPos];
- }
- return false;
- });
- return consoleElement;
- }
- function addEventListenersToElements(elements, event, listener) {
- elements.forEach((el) => el.addEventListener(event, listener));
- }
- /**
- * Add extra info
- */
- function addInfoPrompt(elements) {
- for (let i = 0; i < elements.length; i++) {
- elements[i].innerHTML =
- "<p>To switch between the interactive traceback and the plaintext " +
- 'one, you can click on the "Traceback" headline. From the text ' +
- "traceback you can also create a paste of it. " +
- (!EVALEX
- ? ""
- : "For code execution mouse-over the frame you want to debug and " +
- "click on the console icon on the right side." +
- "<p>You can execute arbitrary Python code in the stack frames and " +
- "there are some extra helpers available for introspection:" +
- "<ul><li><code>dump()</code> shows all variables in the frame" +
- "<li><code>dump(obj)</code> dumps all that's known about the object</ul>");
- elements[i].classList.remove("nojavascript");
- }
- }
- function addConsoleIconToFrames(frames) {
- for (let i = 0; i < frames.length; i++) {
- let consoleNode = null;
- const target = frames[i];
- const frameID = frames[i].id.substring(6);
- for (let j = 0; j < target.getElementsByTagName("pre").length; j++) {
- const img = createIconForConsole();
- img.addEventListener("click", (e) => {
- e.stopPropagation();
- consoleNode = openShell(consoleNode, target, frameID);
- return false;
- });
- target.getElementsByTagName("pre")[j].append(img);
- }
- }
- }
- function slideToggle(target) {
- target.classList.toggle("active");
- }
- /**
- * toggle traceback types on click.
- */
- function addToggleTraceTypesOnClick(elements) {
- for (let i = 0; i < elements.length; i++) {
- elements[i].addEventListener("click", () => {
- document.querySelector("div.traceback").classList.toggle("hidden");
- document.querySelector("div.plain").classList.toggle("hidden");
- });
- elements[i].style.cursor = "pointer";
- document.querySelector("div.plain").classList.toggle("hidden");
- }
- }
- function createConsole() {
- const consoleNode = document.createElement("pre");
- consoleNode.classList.add("console");
- consoleNode.classList.add("active");
- return consoleNode;
- }
- function createConsoleOutput() {
- const output = document.createElement("div");
- output.classList.add("output");
- output.innerHTML = "[console ready]";
- return output;
- }
- function createConsoleInputForm() {
- const form = document.createElement("form");
- form.innerHTML = ">>> ";
- return form;
- }
- function createConsoleInput() {
- const command = document.createElement("input");
- command.type = "text";
- command.setAttribute("autocomplete", "off");
- command.setAttribute("spellcheck", false);
- command.setAttribute("autocapitalize", "off");
- command.setAttribute("autocorrect", "off");
- return command;
- }
- function createIconForConsole() {
- const img = document.createElement("img");
- img.setAttribute("src", "?__debugger__=yes&cmd=resource&f=console.png");
- img.setAttribute("title", "Open an interactive python shell in this frame");
- return img;
- }
- function createExpansionButtonForConsole() {
- const expansionButton = document.createElement("a");
- expansionButton.setAttribute("href", "#");
- expansionButton.setAttribute("class", "toggle");
- expansionButton.innerHTML = " ";
- return expansionButton;
- }
- function createInteractiveConsole() {
- const target = document.querySelector("div.console div.inner");
- while (target.firstChild) {
- target.removeChild(target.firstChild);
- }
- openShell(null, target, 0);
- }
- function handleConsoleSubmit(e, command, frameID) {
- // Prevent page from refreshing.
- e.preventDefault();
- return new Promise((resolve) => {
- // Get input command.
- const cmd = command.value;
- // Setup GET request.
- const urlPath = "";
- const params = {
- __debugger__: "yes",
- cmd: cmd,
- frm: frameID,
- s: SECRET,
- };
- const paramString = Object.keys(params)
- .map((key) => {
- return "&" + encodeURIComponent(key) + "=" + encodeURIComponent(params[key]);
- })
- .join("");
- fetch(urlPath + "?" + paramString)
- .then((res) => {
- return res.text();
- })
- .then((data) => {
- const tmp = document.createElement("div");
- tmp.innerHTML = data;
- resolve(tmp);
- // Handle expandable span for long list outputs.
- // Example to test: list(range(13))
- let wrapperAdded = false;
- const wrapperSpan = document.createElement("span");
- const expansionButton = createExpansionButtonForConsole();
- tmp.querySelectorAll("span.extended").forEach((spanToWrap) => {
- const parentDiv = spanToWrap.parentNode;
- if (!wrapperAdded) {
- parentDiv.insertBefore(wrapperSpan, spanToWrap);
- wrapperAdded = true;
- }
- parentDiv.removeChild(spanToWrap);
- wrapperSpan.append(spanToWrap);
- spanToWrap.hidden = true;
- expansionButton.addEventListener("click", () => {
- spanToWrap.hidden = !spanToWrap.hidden;
- expansionButton.classList.toggle("open");
- return false;
- });
- });
- // Add expansion button at end of wrapper.
- if (wrapperAdded) {
- wrapperSpan.append(expansionButton);
- }
- })
- .catch((err) => {
- console.error(err);
- });
- return false;
- });
- }
- function fadeOut(element) {
- element.style.opacity = 1;
- (function fade() {
- element.style.opacity -= 0.1;
- if (element.style.opacity < 0) {
- element.style.display = "none";
- } else {
- requestAnimationFrame(fade);
- }
- })();
- }
- function fadeIn(element, display) {
- element.style.opacity = 0;
- element.style.display = display || "block";
- (function fade() {
- let val = parseFloat(element.style.opacity) + 0.1;
- if (val <= 1) {
- element.style.opacity = val;
- requestAnimationFrame(fade);
- }
- })();
- }
- function docReady(fn) {
- if (document.readyState === "complete" || document.readyState === "interactive") {
- setTimeout(fn, 1);
- } else {
- document.addEventListener("DOMContentLoaded", fn);
- }
- }
|