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" , ( event ) => {
event . preventDefault ( ) ;
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 ) ;
}
}