]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/civetweb/src/third_party/duktape-1.5.2/debugger/static/webui.js
import 12.2.13 release
[ceph.git] / ceph / src / civetweb / src / third_party / duktape-1.5.2 / debugger / static / webui.js
diff --git a/ceph/src/civetweb/src/third_party/duktape-1.5.2/debugger/static/webui.js b/ceph/src/civetweb/src/third_party/duktape-1.5.2/debugger/static/webui.js
new file mode 100644 (file)
index 0000000..d400c6c
--- /dev/null
@@ -0,0 +1,785 @@
+/*
+ *  Duktape debugger web client
+ *
+ *  Talks to the NodeJS server using socket.io.
+ *
+ *  http://unixpapa.com/js/key.html
+ */
+
+// Update interval for custom source highlighting.
+var SOURCE_UPDATE_INTERVAL = 350;
+
+// Source view
+var activeFileName = null;          // file that we want to be loaded in source view
+var activeLine = null;              // scroll to line once file has been loaded
+var activeHighlight = null;         // line that we want to highlight (if any)
+var loadedFileName = null;          // currently loaded (shown) file
+var loadedLineCount = 0;            // currently loaded file line count
+var loadedFileExecuting = false;    // true if currFileName (loosely) matches loadedFileName
+var loadedLinePending = null;       // if set, scroll loaded file to requested line
+var highlightLine = null;           // highlight line
+var sourceEditedLines = [];         // line numbers which have been modified
+                                    // (added classes etc, tracked for removing)
+var sourceUpdateInterval = null;    // timer for updating source view
+var sourceFetchXhr = null;          // current AJAX request for fetching a source file (if any)
+var forceButtonUpdate = false;      // hack to reset button states
+var bytecodeDialogOpen = false;     // bytecode dialog active
+var bytecodeIdxHighlight = null;    // index of currently highlighted line (or null)
+var bytecodeIdxInstr = 0;           // index to first line of bytecode instructions
+
+// Execution state
+var prevState = null;               // previous execution state ('paused', 'running', etc)
+var prevAttached = null;            // previous debugger attached state (true, false, null)
+var currFileName = null;            // current filename being executed
+var currFuncName = null;            // current function name being executed
+var currLine = 0;                   // current line being executed
+var currPc = 0;                     // current bytecode PC being executed
+var currState = 0;                  // current execution state ('paused', 'running', 'detached', etc)
+var currAttached = false;           // current debugger attached state (true or false)
+var currLocals = [];                // current local variables
+var currCallstack = [];             // current callstack (from top to bottom)
+var currBreakpoints = [];           // current breakpoints
+var startedRunning = 0;             // timestamp when last started running (if running)
+                                    // (used to grey out the source file if running for long enough)
+
+/*
+ *  Helpers
+ */
+
+function formatBytes(x) {
+    if (x < 1024) {
+        return String(x) + ' bytes';
+    } else if (x < 1024 * 1024) {
+        return (x / 1024).toPrecision(3) + ' kB';
+    } else {
+        return (x / (1024 * 1024)).toPrecision(3) + ' MB';
+    }
+}
+
+/*
+ *  Source view periodic update handling
+ */
+
+function doSourceUpdate() {
+    var elem;
+
+    // Remove previously added custom classes
+    sourceEditedLines.forEach(function (linenum) {
+        elem = $('#source-code div')[linenum - 1];
+        if (elem) {
+            elem.classList.remove('breakpoint');
+            elem.classList.remove('execution');
+            elem.classList.remove('highlight');
+        }
+    });
+    sourceEditedLines.length = 0;
+
+    // If we're executing the file shown, highlight current line
+    if (loadedFileExecuting) {
+        elem = $('#source-code div')[currLine - 1];
+        if (elem) {
+            sourceEditedLines.push(currLine);
+            elem.classList.add('execution');
+        }
+    }
+
+    // Add breakpoints
+    currBreakpoints.forEach(function (bp) {
+        if (bp.fileName === loadedFileName) {
+            elem = $('#source-code div')[bp.lineNumber - 1];
+            if (elem) {
+                sourceEditedLines.push(bp.lineNumber);
+                elem.classList.add('breakpoint');
+            }
+        }
+    });
+
+    if (highlightLine !== null) {
+        elem = $('#source-code div')[highlightLine - 1];
+        if (elem) {
+            sourceEditedLines.push(highlightLine);
+            elem.classList.add('highlight');
+        }
+    }
+
+    // Bytecode dialog highlight
+    if (loadedFileExecuting && bytecodeDialogOpen && bytecodeIdxHighlight !== bytecodeIdxInstr + currPc) {
+        if (typeof bytecodeIdxHighlight === 'number') {
+            $('#bytecode-preformatted div')[bytecodeIdxHighlight].classList.remove('highlight');
+        }
+        bytecodeIdxHighlight = bytecodeIdxInstr + currPc;
+        $('#bytecode-preformatted div')[bytecodeIdxHighlight].classList.add('highlight');
+    }
+
+    // If no-one requested us to scroll to a specific line, finish.
+    if (loadedLinePending == null) {
+        return;
+    }
+
+    var reqLine = loadedLinePending;
+    loadedLinePending = null;
+
+    // Scroll to requested line.  This is not very clean, so a better solution
+    // should be found:
+    // https://developer.mozilla.org/en-US/docs/Web/API/Element.scrollIntoView
+    // http://erraticdev.blogspot.fi/2011/02/jquery-scroll-into-view-plugin-with.html
+    // http://flesler.blogspot.fi/2007/10/jqueryscrollto.html
+    var tmpLine = Math.max(reqLine - 5, 0);
+    elem = $('#source-code div')[tmpLine];
+    if (elem) {
+        elem.scrollIntoView();
+    }
+}
+
+// Source is updated periodically.  Other code can also call doSourceUpdate()
+// directly if an immediate update is needed.
+sourceUpdateInterval = setInterval(doSourceUpdate, SOURCE_UPDATE_INTERVAL);
+
+/*
+ *  UI update handling when exec-status update arrives
+ */
+
+function doUiUpdate() {
+    var now = Date.now();
+
+    // Note: loadedFileName can be either from target or from server, but they
+    // must match exactly.  We could do a loose match here, but exact matches
+    // are needed for proper breakpoint handling anyway.
+    loadedFileExecuting = (loadedFileName === currFileName);
+
+    // If we just started running, store a timestamp so we can grey out the
+    // source view only if we execute long enough (i.e. we're not just
+    // stepping).
+    if (currState !== prevState && currState === 'running') {
+        startedRunning = now;
+    }
+
+    // If we just became paused, check for eval watch
+    if (currState !== prevState && currState === 'paused') {
+        if ($('#eval-watch').is(':checked')) {
+            submitEval();  // don't clear eval input
+        }
+    }
+
+    // Update current execution state
+    if (currFileName === '' && currLine === 0) {
+        $('#current-fileline').text('');
+    } else {
+        $('#current-fileline').text(String(currFileName) + ':' + String(currLine));
+    }
+    if (currFuncName === '' && currPc === 0) {
+        $('#current-funcpc').text('');
+    } else {
+        $('#current-funcpc').text(String(currFuncName) + '() pc ' + String(currPc));
+    }
+    $('#current-state').text(String(currState));
+
+    // Update buttons
+    if (currState !== prevState || currAttached !== prevAttached || forceButtonUpdate) {
+        $('#stepinto-button').prop('disabled', !currAttached || currState !== 'paused');
+        $('#stepover-button').prop('disabled', !currAttached || currState !== 'paused');
+        $('#stepout-button').prop('disabled', !currAttached || currState !== 'paused');
+        $('#resume-button').prop('disabled', !currAttached || currState !== 'paused');
+        $('#pause-button').prop('disabled', !currAttached || currState !== 'running');
+        $('#attach-button').prop('disabled', currAttached);
+        if (currAttached) {
+            $('#attach-button').removeClass('enabled');
+        } else {
+            $('#attach-button').addClass('enabled');
+        }
+        $('#detach-button').prop('disabled', !currAttached);
+        $('#eval-button').prop('disabled', !currAttached);
+        $('#add-breakpoint-button').prop('disabled', !currAttached);
+        $('#delete-all-breakpoints-button').prop('disabled', !currAttached);
+        $('.delete-breakpoint-button').prop('disabled', !currAttached);
+        $('#putvar-button').prop('disabled', !currAttached);
+        $('#getvar-button').prop('disabled', !currAttached);
+        $('#heap-dump-download-button').prop('disabled', !currAttached);
+    }
+    if (currState !== 'running' || forceButtonUpdate) {
+        // Remove pending highlight once we're no longer running.
+        $('#pause-button').removeClass('pending');
+        $('#eval-button').removeClass('pending');
+    }
+    forceButtonUpdate = false;
+
+    // Make source window grey when running for a longer time, use a small
+    // delay to avoid flashing grey when stepping.
+    if (currState === 'running' && now - startedRunning >= 500) {
+        $('#source-pre').removeClass('notrunning');
+        $('#current-state').removeClass('notrunning');
+    } else {
+        $('#source-pre').addClass('notrunning');
+        $('#current-state').addClass('notrunning');
+    }
+
+    // Force source view to match currFileName only when running or when
+    // just became paused (from running or detached).
+    var fetchSource = false;
+    if (typeof currFileName === 'string') {
+        if (currState === 'running' ||
+            (prevState !== 'paused' && currState === 'paused') ||
+            (currAttached !== prevAttached)) {
+            if (activeFileName !== currFileName) {
+                fetchSource = true;
+                activeFileName = currFileName;
+                activeLine = currLine;
+                activeHighlight = null;
+                requestSourceRefetch();
+            }
+        }
+    }
+
+    // Force line update (scrollTop) only when running or just became paused.
+    // Otherwise let user browse and scroll source files freely.
+    if (!fetchSource) {
+        if ((prevState !== 'paused' && currState === 'paused') ||
+            currState === 'running') {
+            loadedLinePending = currLine || 0;
+        }
+    }
+}
+
+/*
+ *  Init socket.io and add handlers
+ */
+
+var socket = io();  // returns a Manager
+
+setInterval(function () {
+    socket.emit('keepalive', {
+        userAgent: (navigator || {}).userAgent
+    });
+}, 30000);
+
+socket.on('connect', function () {
+    $('#socketio-info').text('connected');
+    currState = 'connected';
+
+    fetchSourceList();
+});
+socket.on('disconnect', function () {
+    $('#socketio-info').text('not connected');
+    currState = 'disconnected';
+});
+socket.on('reconnecting', function () {
+    $('#socketio-info').text('reconnecting');
+    currState = 'reconnecting';
+});
+socket.on('error', function (err) {
+    $('#socketio-info').text(err);
+});
+
+socket.on('replaced', function () {
+    // XXX: how to minimize the chance we'll further communciate with the
+    // server or reconnect to it?  socket.reconnection()?
+
+    // We'd like to window.close() here but can't (not allowed from scripts).
+    // Alert is the next best thing.
+    alert('Debugger connection replaced by a new one, do you have multiple tabs open? If so, please close this tab.');
+});
+
+socket.on('keepalive', function (msg) {
+    // Not really interesting in the UI
+    // $('#server-info').text(new Date() + ': ' + JSON.stringify(msg));
+});
+
+socket.on('basic-info', function (msg) {
+    $('#duk-version').text(String(msg.duk_version));
+    $('#duk-git-describe').text(String(msg.duk_git_describe));
+    $('#target-info').text(String(msg.target_info));
+    $('#endianness').text(String(msg.endianness));
+});
+
+socket.on('exec-status', function (msg) {
+    // Not 100% reliable if callstack has several functions of the same name
+    if (bytecodeDialogOpen && (currFileName != msg.fileName || currFuncName != msg.funcName)) {
+        socket.emit('get-bytecode', {});
+    }
+
+    currFileName = msg.fileName;
+    currFuncName = msg.funcName;
+    currLine = msg.line;
+    currPc = msg.pc;
+    currState = msg.state;
+    currAttached = msg.attached;
+
+    // Duktape now restricts execution status updates quite effectively so
+    // there's no need to rate limit UI updates now.
+
+    doUiUpdate();
+
+    prevState = currState;
+    prevAttached = currAttached;
+});
+
+// Update the "console" output based on lines sent by the server.  The server
+// rate limits these updates to keep the browser load under control.  Even
+// better would be for the client to pull this (and other stuff) on its own.
+socket.on('output-lines', function (msg) {
+    var elem = $('#output');
+    var i, n, ent;
+
+    elem.empty();
+    for (i = 0, n = msg.length; i < n; i++) {
+        ent = msg[i];
+        if (ent.type === 'print') {
+            elem.append($('<div></div>').text(ent.message));
+        } else if (ent.type === 'alert') {
+            elem.append($('<div class="alert"></div>').text(ent.message));
+        } else if (ent.type === 'log') {
+            elem.append($('<div class="log loglevel' + ent.level + '"></div>').text(ent.message));
+        } else if (ent.type === 'debugger-info') {
+            elem.append($('<div class="debugger-info"><div>').text(ent.message));
+        } else if (ent.type === 'debugger-debug') {
+            elem.append($('<div class="debugger-debug"><div>').text(ent.message));
+        } else {
+            elem.append($('<div></div>').text(ent.message));
+        }
+    }
+
+    // http://stackoverflow.com/questions/14918787/jquery-scroll-to-bottom-of-div-even-after-it-updates
+    // Stop queued animations so that we always scroll quickly to bottom
+    $('#output').stop(true);
+    $('#output').animate({ scrollTop: $('#output')[0].scrollHeight}, 1000);
+});
+
+socket.on('callstack', function (msg) {
+    var elem = $('#callstack');
+    var s1, s2, div;
+
+    currCallstack = msg.callstack;
+
+    elem.empty();
+    msg.callstack.forEach(function (e) {
+        s1 = $('<a class="rest"></a>').text(e.fileName + ':' + e.lineNumber + ' (pc ' + e.pc + ')');  // float
+        s1.on('click', function () {
+            activeFileName = e.fileName;
+            activeLine = e.lineNumber || 1;
+            activeHighlight = activeLine;
+            requestSourceRefetch();
+        });
+        s2 = $('<span class="func"></span>').text(e.funcName + '()');
+        div = $('<div></div>');
+        div.append(s1);
+        div.append(s2);
+        elem.append(div);
+    });
+});
+
+socket.on('locals', function (msg) {
+    var elem = $('#locals');
+    var s1, s2, div;
+    var i, n, e;
+
+    currLocals = msg.locals;
+
+    elem.empty();
+    for (i = 0, n = msg.locals.length; i < n; i++) {
+        e = msg.locals[i];
+        s1 = $('<span class="value"></span>').text(e.value);  // float
+        s2 = $('<span class="key"></span>').text(e.key);
+        div = $('<div></div>');
+        div.append(s1);
+        div.append(s2);
+        elem.append(div);
+    }
+});
+
+socket.on('debug-stats', function (msg) {
+    $('#debug-rx-bytes').text(formatBytes(msg.rxBytes));
+    $('#debug-rx-dvalues').text(msg.rxDvalues);
+    $('#debug-rx-messages').text(msg.rxMessages);
+    $('#debug-rx-kbrate').text((msg.rxBytesPerSec / 1024).toFixed(2));
+    $('#debug-tx-bytes').text(formatBytes(msg.txBytes));
+    $('#debug-tx-dvalues').text(msg.txDvalues);
+    $('#debug-tx-messages').text(msg.txMessages);
+    $('#debug-tx-kbrate').text((msg.txBytesPerSec / 1024).toFixed(2));
+});
+
+socket.on('breakpoints', function (msg) {
+    var elem = $('#breakpoints');
+    var div;
+    var sub;
+
+    currBreakpoints = msg.breakpoints;
+
+    elem.empty();
+
+    // First line is special
+    div = $('<div></div>');
+    sub = $('<button id="delete-all-breakpoints-button"></button>').text('Delete all breakpoints');
+    sub.on('click', function () {
+        socket.emit('delete-all-breakpoints');
+    });
+    div.append(sub);
+    sub = $('<input id="add-breakpoint-file"></input>').val('file.js');
+    div.append(sub);
+    sub = $('<span></span>').text(':');
+    div.append(sub);
+    sub = $('<input id="add-breakpoint-line"></input>').val('123');
+    div.append(sub);
+    sub = $('<button id="add-breakpoint-button"></button>').text('Add breakpoint');
+    sub.on('click', function () {
+        socket.emit('add-breakpoint', {
+            fileName: $('#add-breakpoint-file').val(),
+            lineNumber: Number($('#add-breakpoint-line').val())
+        });
+    });
+    div.append(sub);
+    sub = $('<span id="breakpoint-hint"></span>').text('or dblclick source');
+    div.append(sub);
+    elem.append(div);
+
+    // Active breakpoints follow
+    msg.breakpoints.forEach(function (bp) {
+        var div;
+        var sub;
+
+        div = $('<div class="breakpoint-line"></div>');
+        sub = $('<button class="delete-breakpoint-button"></button>').text('Delete');
+        sub.on('click', function () {
+            socket.emit('delete-breakpoint', {
+                fileName: bp.fileName,
+                lineNumber: bp.lineNumber
+            });
+        });
+        div.append(sub);
+        sub = $('<a></a>').text((bp.fileName || '?') + ':' + (bp.lineNumber || 0));
+        sub.on('click', function () {
+            activeFileName = bp.fileName || '';
+            activeLine = bp.lineNumber || 1;
+            activeHighlight = activeLine;
+            requestSourceRefetch();
+        });
+        div.append(sub);
+        elem.append(div);
+    });
+
+    forceButtonUpdate = true;
+    doUiUpdate();
+});
+
+socket.on('eval-result', function (msg) {
+    $('#eval-output').text((msg.error ? 'ERROR: ' : '') + msg.result);
+
+    // Remove eval button "pulsating" glow when we get a result
+    $('#eval-button').removeClass('pending');
+});
+
+socket.on('getvar-result', function (msg) {
+    $('#var-output').text(msg.found ? msg.result : 'NOTFOUND');
+});
+
+socket.on('bytecode', function (msg) {
+    var elem, div;
+    var div;
+
+    elem = $('#bytecode-preformatted');
+    elem.empty();
+
+    msg.preformatted.split('\n').forEach(function (line, idx) {
+        div = $('<div></div>');
+        div.text(line);
+        elem.append(div);
+    });
+
+    bytecodeIdxHighlight = null;
+    bytecodeIdxInstr = msg.idxPreformattedInstructions;
+});
+
+$('#stepinto-button').click(function () {
+    socket.emit('stepinto', {});
+});
+
+$('#stepover-button').click(function () {
+    socket.emit('stepover', {});
+});
+
+$('#stepout-button').click(function () {
+    socket.emit('stepout', {});
+});
+
+$('#pause-button').click(function () {
+    socket.emit('pause', {});
+
+    // Pause may take seconds to complete so indicate it is pending.
+    $('#pause-button').addClass('pending');
+});
+
+$('#resume-button').click(function () {
+    socket.emit('resume', {});
+});
+
+$('#attach-button').click(function () {
+    socket.emit('attach', {});
+});
+
+$('#detach-button').click(function () {
+    socket.emit('detach', {});
+});
+
+$('#about-button').click(function () {
+    $('#about-dialog').dialog('open');
+});
+
+$('#show-bytecode-button').click(function () {
+    bytecodeDialogOpen = true;
+    $('#bytecode-dialog').dialog('open');
+
+    elem = $('#bytecode-preformatted');
+    elem.empty().text('Loading bytecode...');
+
+    socket.emit('get-bytecode', {});
+});
+
+function submitEval() {
+    socket.emit('eval', { input: $('#eval-input').val() });
+
+    // Eval may take seconds to complete so indicate it is pending.
+    $('#eval-button').addClass('pending');
+}
+
+$('#eval-button').click(function () {
+    submitEval();
+    $('#eval-input').val('');
+});
+
+$('#getvar-button').click(function () {
+    socket.emit('getvar', { varname: $('#varname-input').val() });
+});
+
+$('#putvar-button').click(function () {
+    // The variable value is parsed as JSON right now, but it'd be better to
+    // also be able to parse buffer values etc.
+    var val = JSON.parse($('#varvalue-input').val());
+    socket.emit('putvar', { varname: $('#varname-input').val(), varvalue: val });
+});
+
+$('#source-code').dblclick(function (event) {
+    var target = event.target;
+    var elems = $('#source-code div');
+    var i, n;
+    var line = 0;
+
+    // XXX: any faster way; elems doesn't have e.g. indexOf()
+    for (i = 0, n = elems.length; i < n; i++) {
+        if (target === elems[i]) {
+            line = i + 1;
+        }
+    }
+
+    socket.emit('toggle-breakpoint', {
+        fileName: loadedFileName,
+        lineNumber: line
+    });
+});
+
+function setSourceText(data) {
+    var elem, div;
+
+    elem = $('#source-code');
+    elem.empty();
+    data.split('\n').forEach(function (line) {
+        div = $('<div></div>');
+        div.text(line);
+        elem.append(div);
+    });
+
+    sourceEditedLines = [];
+}
+
+function setSourceSelect(fileName) {
+    var elem;
+    var i, n, t;
+
+    if (fileName == null) {
+        $('#source-select').val('__none__');
+        return;
+    }
+
+    elem = $('#source-select option');
+    for (i = 0, n = elem.length; i < n; i++) {
+        // Exact match is required.
+        t = $(elem[i]).val();
+        if (t === fileName) {
+            $('#source-select').val(t);
+            return;
+        }
+    }
+}
+
+/*
+ *  AJAX request handling to fetch source files
+ */
+
+function requestSourceRefetch() {
+    // If previous update is pending, abort and start a new one.
+    if (sourceFetchXhr) {
+        sourceFetchXhr.abort();
+        sourceFetchXhr = null;
+    }
+
+    // Make copies of the requested file/line so that we have the proper
+    // values in case they've changed.
+    var fileName = activeFileName;
+    var lineNumber = activeLine;
+
+    // AJAX request for the source.
+    sourceFetchXhr = $.ajax({
+        type: 'POST',
+        url: '/source',
+        data: JSON.stringify({ fileName: fileName }),
+        contentType: 'application/json',
+        success: function (data, status, jqxhr) {
+            var elem;
+
+            sourceFetchXhr = null;
+
+            loadedFileName = fileName;
+            loadedLineCount = data.split('\n').length;  // XXX: ignore issue with last empty line for now
+            loadedFileExecuting = (loadedFileName === currFileName);
+            setSourceText(data);
+            setSourceSelect(fileName);
+            loadedLinePending = activeLine || 1;
+            highlightLine = activeHighlight;  // may be null
+            activeLine = null;
+            activeHighlight = null;
+            doSourceUpdate();
+
+            // XXX: hacky transition, make source change visible
+            $('#source-pre').fadeTo('fast', 0.25, function () {
+                $('#source-pre').fadeTo('fast', 1.0);
+            });
+        },
+        error: function (jqxhr, status, err) {
+            // Not worth alerting about because source fetch errors happen
+            // all the time, e.g. for dynamically evaluated code.
+
+            sourceFetchXhr = null;
+
+            // XXX: prevent retry of no-such-file by negative caching?
+            loadedFileName = fileName;
+            loadedLineCount = 1;
+            loadedFileExecuting = false;
+            setSourceText('// Cannot load source file: ' + fileName);
+            setSourceSelect(null);
+            loadedLinePending = 1;
+            activeLine = null;
+            activeHighlight = null;
+            doSourceUpdate();
+
+            // XXX: error transition here
+            $('#source-pre').fadeTo('fast', 0.25, function () {
+                $('#source-pre').fadeTo('fast', 1.0);
+            });
+        },
+        dataType: 'text'
+    });
+}
+
+/*
+ *  AJAX request for fetching the source list
+ */
+
+function fetchSourceList() {
+    $.ajax({
+        type: 'POST',
+        url: '/sourceList',
+        data: JSON.stringify({}),
+        contentType: 'application/json',
+        success: function (data, status, jqxhr) {
+            var elem = $('#source-select');
+
+            data = JSON.parse(data);
+
+            elem.empty();
+            var opt = $('<option></option>').attr({ 'value': '__none__' }).text('No source file selected');
+            elem.append(opt);
+            data.forEach(function (ent) {
+                var opt = $('<option></option>').attr({ 'value': ent }).text(ent);
+                elem.append(opt);
+            });
+            elem.change(function () {
+                activeFileName = elem.val();
+                activeLine = 1;
+                requestSourceRefetch();
+            });
+        },
+        error: function (jqxhr, status, err) {
+            // This is worth alerting about as the UI is somewhat unusable
+            // if we don't get a source list.
+
+            alert('Failed to load source list: ' + err);
+        },
+        dataType: 'text'
+    });
+}
+
+/*
+ *  Initialization
+ */
+
+$(document).ready(function () {
+    var showAbout = true;
+
+    // About dialog, shown automatically on first startup.
+    $('#about-dialog').dialog({
+        autoOpen: false,
+        hide: 'fade',  // puff
+        show: 'fade',  // slide, puff
+        width: 500,
+        height: 300
+    });
+
+    // Bytecode dialog
+    $('#bytecode-dialog').dialog({
+        autoOpen: false,
+        hide: 'fade',  // puff
+        show: 'fade',  // slide, puff
+        width: 700,
+        height: 600,
+        close: function () {
+            bytecodeDialogOpen = false;
+            bytecodeIdxHighlight = null;
+            bytecodeIdxInstr = 0;
+        }
+    });
+
+    // http://diveintohtml5.info/storage.html
+    if (typeof localStorage !== 'undefined') {
+        if (localStorage.getItem('about-shown')) {
+            showAbout = false;
+        } else {
+            localStorage.setItem('about-shown', 'yes');
+        }
+    }
+    if (showAbout) {
+        $('#about-dialog').dialog('open');
+    }
+
+    // onclick handler for exec status text
+    function loadCurrFunc() {
+        activeFileName = currFileName;
+        activeLine = currLine;
+        requestSourceRefetch();
+    }
+    $('#exec-other').on('click', loadCurrFunc);
+
+    // Enter handling for eval input
+    // https://forum.jquery.com/topic/bind-html-input-to-enter-key-keypress
+    $('#eval-input').keypress(function (event) {
+        if (event.keyCode == 13) {
+            submitEval();
+            $('#eval-input').val('');
+        }
+    });
+
+    // Eval watch handling
+    $('#eval-watch').change(function () {
+        // nop
+    });
+
+    forceButtonUpdate = true;
+    doUiUpdate();
+});