codekingpro/portable-devtools
114k
1docReady(() => {2 if (!EVALEX_TRUSTED) {3 initPinBox();4 }5 // if we are in console mode, show the console.6 if (CONSOLE_MODE && EVALEX) {7 createInteractiveConsole();8 }9 10 const frames = document.querySelectorAll("div.traceback div.frame");11 if (EVALEX) {12 addConsoleIconToFrames(frames);13 }14 addEventListenersToElements(document.querySelectorAll("div.detail"), "click", () =>15 document.querySelector("div.traceback").scrollIntoView(false)16 );17 addToggleFrameTraceback(frames);18 addToggleTraceTypesOnClick(document.querySelectorAll("h2.traceback"));19 addInfoPrompt(document.querySelectorAll("span.nojavascript"));20 wrapPlainTraceback();21});22 23function addToggleFrameTraceback(frames) {24 frames.forEach((frame) => {25 frame.addEventListener("click", () => {26 frame.getElementsByTagName("pre")[0].parentElement.classList.toggle("expanded");27 });28 })29}30 31 32function wrapPlainTraceback() {33 const plainTraceback = document.querySelector("div.plain textarea");34 const wrapper = document.createElement("pre");35 const textNode = document.createTextNode(plainTraceback.textContent);36 wrapper.appendChild(textNode);37 plainTraceback.replaceWith(wrapper);38}39 40function initPinBox() {41 document.querySelector(".pin-prompt form").addEventListener(42 "submit",43 function (event) {44 event.preventDefault();45 const pin = encodeURIComponent(this.pin.value);46 const encodedSecret = encodeURIComponent(SECRET);47 const btn = this.btn;48 btn.disabled = true;49 50 fetch(51 `${document.location}?__debugger__=yes&cmd=pinauth&pin=${pin}&s=${encodedSecret}`52 )53 .then((res) => res.json())54 .then(({auth, exhausted}) => {55 if (auth) {56 EVALEX_TRUSTED = true;57 fadeOut(document.getElementsByClassName("pin-prompt")[0]);58 } else {59 alert(60 `Error: ${61 exhausted62 ? "too many attempts. Restart server to retry."63 : "incorrect pin"64 }`65 );66 }67 })68 .catch((err) => {69 alert("Error: Could not verify PIN. Network error?");70 console.error(err);71 })72 .finally(() => (btn.disabled = false));73 },74 false75 );76}77 78function promptForPin() {79 if (!EVALEX_TRUSTED) {80 const encodedSecret = encodeURIComponent(SECRET);81 fetch(82 `${document.location}?__debugger__=yes&cmd=printpin&s=${encodedSecret}`83 );84 const pinPrompt = document.getElementsByClassName("pin-prompt")[0];85 fadeIn(pinPrompt);86 document.querySelector('.pin-prompt input[name="pin"]').focus();87 }88}89 90/**91 * Helper function for shell initialization92 */93function openShell(consoleNode, target, frameID) {94 promptForPin();95 if (consoleNode) {96 slideToggle(consoleNode);97 return consoleNode;98 }99 let historyPos = 0;100 const history = [""];101 const consoleElement = createConsole();102 const output = createConsoleOutput();103 const form = createConsoleInputForm();104 const command = createConsoleInput();105 106 target.parentNode.appendChild(consoleElement);107 consoleElement.append(output);108 consoleElement.append(form);109 form.append(command);110 command.focus();111 slideToggle(consoleElement);112 113 form.addEventListener("submit", (e) => {114 handleConsoleSubmit(e, command, frameID).then((consoleOutput) => {115 output.append(consoleOutput);116 command.focus();117 consoleElement.scrollTo(0, consoleElement.scrollHeight);118 const old = history.pop();119 history.push(command.value);120 if (typeof old !== "undefined") {121 history.push(old);122 }123 historyPos = history.length - 1;124 command.value = "";125 });126 });127 128 command.addEventListener("keydown", (e) => {129 if (e.key === "l" && e.ctrlKey) {130 output.innerText = "--- screen cleared ---";131 } else if (e.key === "ArrowUp" || e.key === "ArrowDown") {132 // Handle up arrow and down arrow.133 if (e.key === "ArrowUp" && historyPos > 0) {134 e.preventDefault();135 historyPos--;136 } else if (e.key === "ArrowDown" && historyPos < history.length - 1) {137 historyPos++;138 }139 command.value = history[historyPos];140 }141 return false;142 });143 144 return consoleElement;145}146 147function addEventListenersToElements(elements, event, listener) {148 elements.forEach((el) => el.addEventListener(event, listener));149}150 151/**152 * Add extra info153 */154function addInfoPrompt(elements) {155 for (let i = 0; i < elements.length; i++) {156 elements[i].innerHTML =157 "<p>To switch between the interactive traceback and the plaintext " +158 'one, you can click on the "Traceback" headline. From the text ' +159 "traceback you can also create a paste of it. " +160 (!EVALEX161 ? ""162 : "For code execution mouse-over the frame you want to debug and " +163 "click on the console icon on the right side." +164 "<p>You can execute arbitrary Python code in the stack frames and " +165 "there are some extra helpers available for introspection:" +166 "<ul><li><code>dump()</code> shows all variables in the frame" +167 "<li><code>dump(obj)</code> dumps all that's known about the object</ul>");168 elements[i].classList.remove("nojavascript");169 }170}171 172function addConsoleIconToFrames(frames) {173 for (let i = 0; i < frames.length; i++) {174 let consoleNode = null;175 const target = frames[i];176 const frameID = frames[i].id.substring(6);177 178 for (let j = 0; j < target.getElementsByTagName("pre").length; j++) {179 const img = createIconForConsole();180 img.addEventListener("click", (e) => {181 e.stopPropagation();182 consoleNode = openShell(consoleNode, target, frameID);183 return false;184 });185 target.getElementsByTagName("pre")[j].append(img);186 }187 }188}189 190function slideToggle(target) {191 target.classList.toggle("active");192}193 194/**195 * toggle traceback types on click.196 */197function addToggleTraceTypesOnClick(elements) {198 for (let i = 0; i < elements.length; i++) {199 elements[i].addEventListener("click", () => {200 document.querySelector("div.traceback").classList.toggle("hidden");201 document.querySelector("div.plain").classList.toggle("hidden");202 });203 elements[i].style.cursor = "pointer";204 document.querySelector("div.plain").classList.toggle("hidden");205 }206}207 208function createConsole() {209 const consoleNode = document.createElement("pre");210 consoleNode.classList.add("console");211 consoleNode.classList.add("active");212 return consoleNode;213}214 215function createConsoleOutput() {216 const output = document.createElement("div");217 output.classList.add("output");218 output.innerHTML = "[console ready]";219 return output;220}221 222function createConsoleInputForm() {223 const form = document.createElement("form");224 form.innerHTML = ">>> ";225 return form;226}227 228function createConsoleInput() {229 const command = document.createElement("input");230 command.type = "text";231 command.setAttribute("autocomplete", "off");232 command.setAttribute("spellcheck", false);233 command.setAttribute("autocapitalize", "off");234 command.setAttribute("autocorrect", "off");235 return command;236}237 238function createIconForConsole() {239 const img = document.createElement("img");240 img.setAttribute("src", "?__debugger__=yes&cmd=resource&f=console.png");241 img.setAttribute("title", "Open an interactive python shell in this frame");242 return img;243}244 245function createExpansionButtonForConsole() {246 const expansionButton = document.createElement("a");247 expansionButton.setAttribute("href", "#");248 expansionButton.setAttribute("class", "toggle");249 expansionButton.innerHTML = " ";250 return expansionButton;251}252 253function createInteractiveConsole() {254 const target = document.querySelector("div.console div.inner");255 while (target.firstChild) {256 target.removeChild(target.firstChild);257 }258 openShell(null, target, 0);259}260 261function handleConsoleSubmit(e, command, frameID) {262 // Prevent page from refreshing.263 e.preventDefault();264 265 return new Promise((resolve) => {266 // Get input command.267 const cmd = command.value;268 269 // Setup GET request.270 const urlPath = "";271 const params = {272 __debugger__: "yes",273 cmd: cmd,274 frm: frameID,275 s: SECRET,276 };277 const paramString = Object.keys(params)278 .map((key) => {279 return "&" + encodeURIComponent(key) + "=" + encodeURIComponent(params[key]);280 })281 .join("");282 283 fetch(urlPath + "?" + paramString)284 .then((res) => {285 return res.text();286 })287 .then((data) => {288 const tmp = document.createElement("div");289 tmp.innerHTML = data;290 resolve(tmp);291 292 // Handle expandable span for long list outputs.293 // Example to test: list(range(13))294 let wrapperAdded = false;295 const wrapperSpan = document.createElement("span");296 const expansionButton = createExpansionButtonForConsole();297 298 tmp.querySelectorAll("span.extended").forEach((spanToWrap) => {299 const parentDiv = spanToWrap.parentNode;300 if (!wrapperAdded) {301 parentDiv.insertBefore(wrapperSpan, spanToWrap);302 wrapperAdded = true;303 }304 parentDiv.removeChild(spanToWrap);305 wrapperSpan.append(spanToWrap);306 spanToWrap.hidden = true;307 308 expansionButton.addEventListener("click", (event) => {309 event.preventDefault();310 spanToWrap.hidden = !spanToWrap.hidden;311 expansionButton.classList.toggle("open");312 return false;313 });314 });315 316 // Add expansion button at end of wrapper.317 if (wrapperAdded) {318 wrapperSpan.append(expansionButton);319 }320 })321 .catch((err) => {322 console.error(err);323 });324 return false;325 });326}327 328function fadeOut(element) {329 element.style.opacity = 1;330 331 (function fade() {332 element.style.opacity -= 0.1;333 if (element.style.opacity < 0) {334 element.style.display = "none";335 } else {336 requestAnimationFrame(fade);337 }338 })();339}340 341function fadeIn(element, display) {342 element.style.opacity = 0;343 element.style.display = display || "block";344 345 (function fade() {346 let val = parseFloat(element.style.opacity) + 0.1;347 if (val <= 1) {348 element.style.opacity = val;349 requestAnimationFrame(fade);350 }351 })();352}353 354function docReady(fn) {355 if (document.readyState === "complete" || document.readyState === "interactive") {356 setTimeout(fn, 1);357 } else {358 document.addEventListener("DOMContentLoaded", fn);359 }360}361 