jsr  A JavaScript REPL

Show / Hide

I should mention: This project has been developed on Chrome Version 20.0.1127.0 canary and Safari Version 5.1.2 (6534.52.7).

// can be just a string:
// var jsrOptions = 'demo';

var jsrOptions = {
    id: 'someId',
    useiScroll: false
};

// a console utility that can be embedded into an html page,
// useful for diagnostics when unable to run a real debugger,
// for example when creating content that is hosted in a 3rd
// party app

(function() {

    var memId = 'jsr_',
        jsrOptions,
        traceTarget_,
        consIn_,
        jsrWindow_,
        histSelect_,
        cmdSelect_,
        scrollMgr,

        callStack = [],
        catchingErrors = false,
        origFuncCall = Function.prototype.call,
        origFuncApply = Function.prototype.apply,
        origWindowError,

        cmdNames = [],
        cmds = [],
        cmdKeyListener,
        
        cfg = {
            winTop: '90px',
            winLeft: '30px',
            winW: '400px',
            winH: '400px',
            catchErrors: false,
            histMax: 100,
            histMemIdx: -1,
            display: 'block'
        },
        histCycleIdx = NaN,
        api;

    function catchErrors(toggleValue) {

        if (toggleValue === undefined) {

            return catchingErrors;

        } else {

            if (catchingErrors !== toggleValue) {

                cfg.catchErrors = toggleValue;
                saveCfg();
                catchingErrors = toggleValue;

                if (toggleValue) {

                    Function.prototype.call = funcCallWrapper;
                    Function.prototype.oCall = origFuncCall;
                    
                    Function.prototype.apply = funcApplyWrapper;
                    Function.prototype.oApply = origFuncApply;

                    origWindowError = window.onerror;
                    window.onerror = handleWindowError;

                } else {

                    Function.prototype.call = origFuncCall;
                    Function.prototype.apply = origFuncApply;
                    window.onerror = origWindowError;
                }
            }
            return api;
        }
    }

    function funcCallWrapper(thisArg) {
        var res;
        try {
            var toStr = fnToShortStr(this),
                callIdx = callStack.length,
                callInfo = {idx: callStack.length, fn: this},
                args;

            if (arguments.length > 1) {
                args = Array.prototype.slice.oApply(arguments, [1]);
                callInfo.args = args;
            }
            Array.prototype.unshift.oCall(callStack, callInfo);
            res = this.oApply(thisArg, args);
            Array.prototype.shift.oCall(callStack);
        } catch (x) {
            logError(x, Array.prototype.shift.oCall(callStack));
            throw x;
        }
        // not reached if an error was encountered bc throws x
        return res;
    }

    function funcApplyWrapper(thisArg, args) {
        var res;
        try {
            var toStr = fnToShortStr(this),
                callIdx = callStack.length;

            Array.prototype.unshift.oCall(callStack, {
                idx: callStack.length,
                fn: this,
                args: args
            });
            res = this.oApply(thisArg, args);
            Array.prototype.shift.oCall(callStack);
            // return res;
        } catch (x) {
            logError(x, Array.prototype.shift.oCall(callStack));
            throw x;
        }
        // not reached if an error was encountered bc throws x
        return res;
    }

    function logError(x, callInfo) {

        scrollMgr.notePos();
        traceTarget_.append('<div class="jsr_co_line_error"><div class="jsr_co_line_error_boundary"></div><div class="jsr_cons_errorIcon"></div><p class="jsr_co_errorInfo">Log Error:</p></div>');


        classyTrace([x], 'jsr_co_line_error');

        if (callInfo) {
            traceTarget_.append('<div class="jsr_co_line_error"><p class="jsr_co_errorInfo">Callstack data:</p></div>');
            traceExpanded(callInfo, 'jsr_co_line_error');
        }
        scrollMgr.refresh();
    }

    function handleWindowError() {

        traceTarget_.append('<div class="jsr_co_line_error"><div class="jsr_co_line_error_boundary"></div><div class="jsr_cons_errorIcon"></div><p class="jsr_co_errorInfo">Unhandled ERRROR:</p></div>');

        traceExpanded(arguments, 'jsr_co_line_error');
        scrollMgr.refresh(true);
    }

    function trace() {
        return classyTrace(arguments);
    }

    function classyTrace(args, coClass) {

        var out;

        coClass = coClass || 'jsr_co_line';

        if (args.length) {
            
            if (args.length > 1) {
                out = Array.prototype.join.oCall(args, [' ']);
            } else if (typeof args[0] === 'string') {
                out = args[0];
            } else {
                out = render(args[0]);
            }
        } else if (typeof args === 'string') {

            out = args;

        } else if (args || args === 0) {

            out = render(args);
        }

        scrollMgr.notePos();
        traceTarget_.append( $('<div class="' + coClass + '"></div>').append(out) );
        scrollMgr.refresh();
        return api;
    }

    function traceInput(value) {
        value = '<span class="jsr_cons_inVal"><span class="jsr_cons_inIcon"></span>' + value + '</span>';
        traceTarget_.append('<div class="jsr_co_line_input"><div class="jsr_co_line_input_boundary"></div><p class="jsr_co_basic">' + value + '</p></div>');
    }

    function traceExpanded(data, coClass) {

        scrollMgr.notePos();
        var div_ = $('<div class="' + (coClass || 'jsr_co_line') + '"></div>').append(render(data)),
            type = $.type(data);

        if (type === 'function' ||
            type === 'object' ||
            type === 'array') {

            div_.append(renderDeep(data, 0));
        }

        traceTarget_.append(div_);
        scrollMgr.refresh();
    }

    function render(value, propName, elmDepth) {

        elmDepth = elmDepth ? elmDepth : 0;

        var endProp,
            type = $.type(value),
            depthStyle = 'jsr_co_propLvl' + elmDepth,
            fnTag,
            fnA;


        if (propName) {
            propName = '<span class="jsr_propName">' + propName + ': </span>';
            endProp = '</span>';
        } else {
            propName = '';
            endProp = '';
        }

        switch ($.type(value)) {
            case 'object':
                return $('<a href="#" class="jsr_consExpander ' + depthStyle + '"></a><a href="#" class="jsr_objectLink">' +
                            propName + '<span>' + value + '</span></a>')
                        .on('click', [value, elmDepth], dataClicked);

            case 'function':
                return $('<a href="#" class="jsr_consExpander ' + depthStyle + '"></a><a href="#" class="jsr_objectLink">' +
                            propName + '<span>' + fnToShortStr(value) + '</span></a>')
                        .on('click', [value, elmDepth], dataClicked);

            case 'array':
                var tos = value.toString();
                tos = propName + '<span>[' + tos.substr(0, 40) + (tos.length > 40 ? '...' : '') + ']' + endProp;
                return $('<a href="#" class="jsr_consExpander ' + depthStyle + '"></a><a href="#" class="jsr_objectLink">' + tos + '</a>')
                        .on('click', [value, elmDepth], dataClicked);

            case 'undefined':
                return propName + '<span>undefined</span>';

            case 'null':
                return propName + '<span>null</span>';

            case 'string':
                return propName + '<span>' + '"' + value + '"</span>';

            case 'date':
                return propName + '<span>' + value + '</span>';

            case 'regexp':
                return propName + '<span>' + value + '</span>';

            case 'boolean':
                return propName + '<span>' + value + '</span>';

            case 'number':
                return propName + '<span>' + value + '</span>';
        }
    }

    function renderDeep(data, depth) {
        var ul_ = $('<ul class="jsr_co_propList"></ul>');
        var li_;

        if ($.type(data) === 'function') {
            li_ = $('<li class="jsr_co_fnBody cm-s-default"></li>');
            CodeMirror.runMode(data.toString(), 'text/javascript', li_[0]);
            ul_.append(li_);
        }

        for (var p in data) {
            ul_.append( $('<li class="jsr_co_oProp"></li>').append(render(data[p], p, depth + 1)) );
        }
        ul_.addClass('jsr_co_zLvl' + depth);
        return ul_;
    }

    function fnToShortStr(fn) {
        var toStr = fn.toString.oCall(fn);
        toStr = toStr.replace.oCall(toStr, /\n+|\t+|\r+| {2,}/gm, ' ');
        var cut = toStr.length > 50;
        toStr = cut ? toStr.substr.oCall(toStr, 0, toStr.lastIndexOf.oCall(toStr, ' ', 50)) : toStr;
        toStr = toStr.trim.oCall(toStr);
        return cut ? toStr + '...' : toStr;
    }

    function dataClicked(event) {

        var uls_ = $(this).parent().find('> ul'),
            data,
            depth;

        if (uls_.length) {

            uls_.toggle();

        } else {

            data = event.data[0];
            depth = event.data[1];
            $(this).parent().append( renderDeep(data, depth));
        }
        return false;
    }

    function seeStackClone() { return callStack.slice(); }

    function cmdSelected(event) {
        var select_ = $(event.currentTarget);
        var val = select_.find('>option:selected').val();
        if (val === '@na') {
            return;
        }
        var consVal = consIn_.val().trimRight();
        select_.val('@na');

        consIn_.val(val);
        consIn_.focus();
    }

    function addCmd(alias, fn, addSnippet) {
        var idx = cmdNames.indexOf(alias);
        if (idx < 0) {
            cmdNames.push(alias);
            cmds.push(fn);
            if (addSnippet) {
                cmdSelect_.prepend(
                    $('<option></option>').attr('value', alias).text(alias)
                );
            }
        } else {
            cmds[idx] = fn;
        }
        return api;
    }

    function addSnippet(value) {

        var existing = cmdSelect_.find('option [value="' + value + '"]');
        if (!existing.length) {
            cmdSelect_.prepend(
                $('<option></option>').attr('value', value).text(value)
            );
        }
        return api;
    }

    function isVisible() {
        return jsrWindow_.css('display') === 'block';
    }

    function setVis(value) {
        var toDisplay = value ? 'block' : 'none';
        jsrWindow_.css({display: toDisplay});
        cfg.display = toDisplay;
        saveCfg();
    }

    function cmd_vis(value) {
        if (value !== undefined) {
            value = value === true || value === 'true';
            setVis(value);
            return api;
        }
        var cur = isVisible();
        trace('Current visibility: ' + cur);
        return cur;
    }

    function cmd_cls() {
        // note: remove() also removes event listeners and releases references to the data used by them
        traceTarget_.find('*').remove();
        return 'cls';
    }

    function cmd_errs(val) {
        if (val !== undefined) {
            catchErrors(val === 'true');
        }
        trace('Error catching enabled: ' + catchErrors());
        return true;
    }

    // scroll command
    // when invoked start listening for arrow input from keypress
    // and scrolls accordingly, end on enter
    var cmd_scroll = (function () {

        var shiftPct = 0.2,
            shiftSize,
            scrollArrowListener = function scrollArrowListener(event) {

                // holding shift increasing scroll amount
                var shiftAmt = shiftSize * (event.shiftKey ? 5 : 1);

                // if is left right down or up, do something;
                switch (event.keyCode) {
                    // enter key
                    case 13:
                        cmdKeyListener = null;
                        jsr('Scroll command exit.');
                        return false;

                    // left arrow
                    case 37:
                        scrollMgr.scrollTo(-shiftAmt, 0, 300, true);
                        return true;

                    // right arrow
                    case 39:
                        scrollMgr.scrollTo(shiftAmt, 0, 300, true);
                        return true;

                    // down arrow
                    case 40:
                        scrollMgr.scrollTo(0, shiftAmt, 300, true);
                        return true;

                    // up arrow
                    case 38:
                        scrollMgr.scrollTo(0, -shiftAmt, 300, true);
                        return true;

                    // for all other keys, continue within command
                    default:
                        return true;
                }
            };

        return function scrollCommand() {

            // start listening for arrow keys, end listening on enter
            jsr('Use the arrow keys to sroll the window.<br/>Hold shift to scroll faster.<br/>Press enter to exit command.');
            cmdKeyListener = scrollArrowListener;
            shiftSize = scrollMgr.getViewHeight() * shiftPct;
            // return false tells not to scroll to bottom
            // (dont want to lose current position)
            return false;
        };
    })();



    // size command
    // when invoked can be passed arguments stating a new size
    // if numeric values are surrounded by quotes then size is relative.
    // if invoked without arguments listens for arrow input from keypress
    // and moves accordingly

    var cmd_size = (function () {

        var shiftSize = 5,
            sizeArrowListener = function sizeArrowListener(event) {

                // holding shift increases size change to 5x
                var shiftAmt = shiftSize * (event.shiftKey ? 5 : 1);

                // if is left right down or up, do something;
                switch (event.keyCode) {
                    // enter key
                    case 13:
                        cmdKeyListener = null;
                        cfg.winW = jsrWindow_.width();
                        cfg.winH = jsrWindow_.height();
                        saveCfg();
                        jsr('Size command exit.');
                        return false;

                    // left arrow
                    case 37:
                        jsrWindow_.width(jsrWindow_.width() - shiftAmt);
                        scrollMgr.refresh();
                        return true;

                    // right arrow
                    case 39:
                        jsrWindow_.width(jsrWindow_.width() + shiftAmt);
                        scrollMgr.refresh();
                        return true;

                    // down arrow
                    case 40:
                        jsrWindow_.height(jsrWindow_.height() + shiftAmt);
                        scrollMgr.refresh();
                        return true;

                    // up arrow
                    case 38:
                        jsrWindow_.height(jsrWindow_.height() - shiftAmt);
                        scrollMgr.refresh();
                        return true;

                    // for all other keys, continue within command
                    default:
                        return true;
                }
            };

        return function sizeCommand() {

            var arg,
                w,
                h;

            // if supplied with arguments on new size, then don't listen for arrow keys
            if (arguments.length) {

                arg = arguments[0];

                if (arg.search(/"|'/g) > -1) {

                    arg = arg.replace(/"|'/g, '');
                    w = jsrWindow_.width() + Number(arg);
                    jsrWindow_.width(w);

                } else {

                    w = Number(arg);
                    jsrWindow_.width(w);
                }
                cfg.winW = w;

                if (arguments.length > 1) {

                    arg = arguments[1];

                    if (arg.search(/"|'/g) > -1) {

                        arg = arg.replace(/"|'/g, '');
                        h = jsrWindow_.height() + Number(arg);
                        jsrWindow_.height(h);

                    } else {

                        h = Number(arg);
                        jsrWindow_.height(h);
                    }
                    cfg.winH = h;
                }
                saveCfg();

            } else {

                // start listening for arrow keys, end listening on enter
                jsr('Use the arrow keys to resize the window.<br/>Hold shift for a larger change in size.<br/>Press enter to exit command.');
                cmdKeyListener = sizeArrowListener;
            }
            // return true tells to scroll to bottom
            return true;
        };
    })();



    // move command
    // when invoked can be passed arguments stating where to move
    // if numeric values are surrounded by quotes then move is relative.
    // if invoked without arguments listens for arrow input from keypress
    // and moves accordingly

    var cmd_move = (function () {

        var shiftSize = 5,
            moveArrowListener = function moveArrowListener(event) {

                var pos = jsrWindow_.offset(),
                    // holding shift increases move amount to 5x
                    shiftAmt = shiftSize * (event.shiftKey ? 5 : 1);

                // if is left right down or up, do something;
                switch (event.keyCode) {
                    // enter key
                    case 13:
                        cmdKeyListener = null;
                        cfg.winTop = pos.top;
                        cfg.winLeft = pos.left;
                        saveCfg();
                        jsr('Move command exit.');
                        return false;

                    // left arrow
                    case 37:
                        pos.left -= shiftAmt;
                        jsrWindow_.offset(pos);
                        return true;

                    // right arrow
                    case 39:
                        pos.left += shiftAmt;
                        jsrWindow_.offset(pos);
                        return true;

                    // down arrow
                    case 40:
                        pos.top += shiftAmt;
                        jsrWindow_.offset(pos);
                        return true;

                    // up arrow
                    case 38:
                        pos.top -= shiftAmt;
                        jsrWindow_.offset(pos);
                        return true;

                    // for all other keys, continue within command
                    default:
                        return true;
                }
            };

        return function moveCommand() {

            var arg,
                pos;

            // if supplied with arguments on where to move, then don't listen for arrow keys
            if (arguments.length) {

                pos = jsrWindow_.offset();
                arg = arguments[0];

                if (arg.search(/"|'/g) > -1) {

                    arg = arg.replace(/"|'/g, '');
                    pos.left += Number(arg);

                } else {

                    pos.left = Number(arg);
                }

                if (arguments.length > 1) {

                    arg = arguments[1];

                    if (arg.search(/"|'/g) > -1) {

                        arg = arg.replace(/"|'/g, '');
                        pos.top += Number(arg);
                    } else {
                        pos.top = Number(arg);
                    }
                }
                jsrWindow_.offset(pos);
                cfg.winTop = pos.top;
                cfg.winLeft = pos.left;
                saveCfg();

            } else {

                // start listening for arrow keys, end listening on enter
                jsr('Use the arrow keys to move the window.<br/>Hold shift to move further.<br/>Press enter to exit command.');
                cmdKeyListener = moveArrowListener;
            }
            // return true tells to scroll to bottom
            return true;
        };
    })();

    function initCommands() {
        addCmd('.mv', cmd_move);
        addCmd('.move', cmd_move, true);
        addCmd('.sz', cmd_size);
        addCmd('.size', cmd_size, true);
        addCmd('.scroll', cmd_scroll, true);
        addCmd('...', cmd_scroll);
        addCmd('.cls', cmd_cls, true);
        addCmd('.errs', cmd_errs, true);
        addCmd('.vis', cmd_vis, true);
    }

    function isString(input) {
        return typeof(input) === 'string';
    }

    function addToHist(cmd) {

        cfg.histMemIdx = (cfg.histMemIdx + 1) % cfg.histMax;
        hist.set(cfg.histMemIdx, cmd);
        saveCfg();

        histSelect_.find('option[value="@na"]').before(createHistOpt(cmd));

        var ops = histSelect_.find('option'),
            cut = ops.length - cfg.histMax - 1;

        if (cut > 0) {
            ops.filter(':lt(' + cut + ')').remove();
        }
    }

    function createHistOpt(cmd) {
        return $('<option></option>').attr('value', cmd).text(cmd);
    }

    function seeCfgClone() {
        return $.extend({}, cfg);
    }

    var mem = (function jsrLocalStorage() {

        var hadVal,
            sawLsErr = false,
            memFunc = function getSet(key, value) {
                if (value !== undefined) {
                    try {
                        return localStorage[memId + key] = value;
                    } catch (err) {
                        if (!sawLsErr) {
                            jsr('Unable to write to localStorage: ').deep(err);
                            sawLsErr = true;
                        }
                    }
                } else {
                    return localStorage[memId + key];
                }
            };

        memFunc.has = function has(k) {
            hadVal = localStorage[memId + k];
            return hadVal !== undefined;
        };

        memFunc.had = function had() { return hadVal; };

        return memFunc;
    })();


    var hist = (function jsrCommandHistory(){

        var hadVal,
            histFunc = function get(idx) {
                return mem('hist' + idx);
            };

        histFunc.get = histFunc;

        histFunc.has = function has(idx) {
            return mem.has('hist' + idx);
        };

        histFunc.had = function had() {
            return mem.had();
        };

        histFunc.set = function set(idx, val) {
            mem('hist' + idx, val);
            return idx;
        };

        histFunc.memPos = function memPos(value) {
            if (value !== undefined && !isNaN(value)) {
                cfg.histMemIdx = value;
                saveCfg();
            }
            return cfg.histMemIdx;
        };

        histFunc.max = function max(value) {
            if (value !== undefined && !isNaN(value)) {
                cfg.histMax = value;
                saveCfg();
            }
            return cfg.histMax;
        };

        return histFunc;
    })();

    function setCfg(a, b) {
        if ($.type(a) === 'object') {
            for (var p in a) {
                cfg[p] = a[p];
            }
            saveCfg();
        } else if ($.type(a) === 'string') {
            cfg[a] = b;
            saveCfg();
        }
        return api;
    }

    function clearCfg(confirm) {
        if (confirm === true || confirm === 'true') {
            for (var p in cfg) {
                delete cfg[p];
            }
            saveCfg();
        }
        return api;
    }

    function initCfg() {
        if (mem.has('cfg')) {
            var fromMem = JSON.parse(mem.had());
            for (var p in fromMem) {
                cfg[p] = fromMem[p];
            }
        }

        // toggle catch errors if is set to true
        catchErrors(cfg.catchErrors);

        // history keeps last cfg.histMax # of commands
        // this is stored in local storage in props named
        // <memId>hist0 to <memId>hist<cfg.histMax>
        // this is cycled through so only cfg.histMax values are ever stored
        // in local storage. When cfg.histMemIdx reaches the max it is reset
        // to 0 and starts to save there. At any point, if there is a value
        // stored at cfg.histMemIdx+1, that is the oldest value
        var i,
            pos,
            naOpt_,
            history = [];

        // validate current index:
        cfg.histMemIdx %= cfg.histMax;

        for (i = 0; i < cfg.histMax; i++) {
            if (hist.has(i)) {
                history[i] = hist.had();
            }
        }
        if (history.length) {

            naOpt_ = histSelect_.find('option[value="@na"]');

            // add oldest values first, ie:
            // add from cfg.histMemIdx+1 to last entry in stored values
            for (i = cfg.histMemIdx + 1; i < history.length; i++) {
                naOpt_.before(createHistOpt(history[i]));
            }
            // then add from 0 to cfg.histMemIdx
            for (i = 0; i <= cfg.histMemIdx && hist.has(i); i++) {
                naOpt_.before(createHistOpt(history[i]));
            }
        }
    }

    function saveCfg() {
        mem('cfg', JSON.stringify(cfg));
    }

    var sizeBound = (function(){
        var minW = 220,
            defaultW = 600,
            minH = 135,
            defaultH = 600;

        return {
            w: function(value) {
                return !isNaN(value) ? Math.max(minW, value) : defaultW;
            },
            h: function(value) {
                return !isNaN(value) ? Math.max(minH, value) : defaultH;
            }
        };

    })();

    function initWindowDrag() {

        var topOffset = 0,
            leftOffset = 0,
            jsrWindow_ = $('#jsr'),
            chromeTop_ = $('#jsr_chrome_top');

        var startWindowDrag = function(e) {
            var pos = jsrWindow_.offset();
            topOffset = e.originalEvent.changedTouches[0].pageY - pos.top;
            leftOffset = e.originalEvent.changedTouches[0].pageX - pos.left;
            jsrWindow_.css('opacity', 0.4);
        };

        var dragWindow = function(e) {
            e.preventDefault();
            var toTop = e.originalEvent.changedTouches[0].pageY - topOffset;
            var toLeft = e.originalEvent.changedTouches[0].pageX - leftOffset;
            jsrWindow_.offset({  top: toTop, left: toLeft  });
        };

        var dragEnd = function(e) {
            var pos = jsrWindow_.offset();
            cfg.winTop = pos.top - $(window).scrollTop();
            cfg.winLeft = pos.left - $(window).scrollLeft();
            jsrWindow_.css('opacity', 1);
            saveCfg();
        };

        var onMouseDown = function(e) {
            $('body')
                .on("mousemove", onMouseMove)
                .on("mouseup", onMouseUp);

            e.originalEvent.changedTouches = [e.originalEvent];
            startWindowDrag(e);
        };

        var onMouseMove = function(e) {
            e.originalEvent.changedTouches = [e.originalEvent];
            dragWindow(e);
        };

        var onMouseUp = function(e) {
            $('body')
                .off("mousemove", onMouseMove)
                .off("mouseup", onMouseUp);
                
            dragEnd(e);
        };

        chromeTop_
            .on("touchstart", startWindowDrag)
            .on("touchmove", dragWindow)
            .on("touchend", dragEnd)
            .on("touchcancel", dragEnd)
            .on("mousedown", onMouseDown);
    }

    function initResizeDrag() {

        var wOffset = 0,
            hOffset = 0,
            jsrWindow_ = $('#jsr'),
            resizeHandle_ = $('#resizeHandle');

        var startWindowResize = function(e) {
            e.preventDefault();
            wOffset = e.originalEvent.changedTouches[0].pageX - jsrWindow_.width();
            hOffset = e.originalEvent.changedTouches[0].pageY - jsrWindow_.height();
        };

        var resizeWindow = function(e) {
            e.preventDefault();
            var w = sizeBound.w(e.originalEvent.changedTouches[0].pageX - wOffset);
            var h = sizeBound.h(e.originalEvent.changedTouches[0].pageY - hOffset);
            jsrWindow_.width(w);
            jsrWindow_.height(h);
            scrollMgr.refresh();
        };

        var resizeEnd = function(e) {
            e.preventDefault();
            cfg.winW = jsrWindow_.width();
            cfg.winH = jsrWindow_.height();
            saveCfg();
        };

        var onMouseDown = function(e) {

            $('body')
                .on("mousemove", onMouseMove)
                .on("mouseup", onMouseUp);

            e.originalEvent.changedTouches = [e.originalEvent];
            startWindowResize(e);
        };

        var onMouseMove = function(e) {
            e.originalEvent.changedTouches = [e.originalEvent];
            resizeWindow(e);
        };

        var onMouseUp = function(e) {

            $('body')
                .off("mousemove", onMouseMove)
                .off("mouseup", onMouseUp);

            resizeEnd(e);
        };

        resizeHandle_
            .on("touchstart", startWindowResize)
            .on("touchmove", resizeWindow)
            .on("touchend", resizeEnd)
            .on("touchcancel", resizeEnd)
            .on("mousedown", onMouseDown);
    }

    /*
    =========
    init core
    =========
    */
    (function() {

        var isiPad = navigator.userAgent.match(/iPad/i) !== null,
            icon_,
            // need something quick and dirty for now
            jsrHtml =  '<div id="jsr"' + (isiPad ? ' class="iPad">' : ' >') +
                            '<div id="jsr_scroller_wrap">' +
                                '<div id="jsr_scroller">' +
                                    '<div id="jsr_scroller_inr">' +
                                        '<div id="jsr_cons_out"></div>' +
                                    '</div>' +
                                '</div>' +
                            '</div>' +
                            '<div id="jsr_cons_in_chrome">' +
                                '<div id="jsr_cons_in_wrap">' +
                                    '<input id="jsr_cons_in" type="text" autocapitalize="off" autocorrect="off" autocomplete="off" />' +
                                    '<div id="jsr_cons_ps1" class="jsrSprite" ></div>  ' +
                                '</div>' +
                                '<div id="historySelectGraphic"></div>' +
                                '<select id="historySelect" required="false">' +
                                    '<option value="@na" selected="selected">Command history:</option>' +
                                '</select>' +
                                '<select id="jsr_cmd_list" required="false">' +
                                    '<option value="@na" selected="selected">Quick C̷̙̲̝͖ͭ̏ͥͮ͟Oͮ͏̮̪̝͍M̲̖͊̒ͪͩͬ̚̚͜M̖͊̒ͬ̚̚͜A̡͊͠͝N̐DS̨̥̫͎̭ͯ̿̔̀ͅ:</option>' +
                                    '<option value=".cls">.cls</option>' +
                                    '<option value="localStorage">localStorage</option>' +
                                    '<option value="jsr(">jsr(</option>' +
                                '</select>' +
                                '<div id="resizeHandle"><span id="resizeHandleIcon"></span></div>' +
                            '</div>' +
                            '<script id="jsr_script" type="text/javascript"></script>' +
                        '<a href="#" onclick="return false;" id="jsr_chrome_top">' +
                            '<div id="jsr_close"></div>' +
                        '</a>' +
                    '</div>',
            jsrIconHtml = '<div id="jsr_icon"></div>',
            prop;

        jsrOptions = {
            id: 'gbl_',
            useiScroll: true
        };
        if (typeof window.jsrOptions === 'string') {
            jsrOptions.id = window.jsrOptions;
            memId = 'jsr_' + window.jsrOptions + '_';
        } else if (window.jsrOptions) {
            for (prop in window.jsrOptions) {
                jsrOptions[prop] = window.jsrOptions[prop];
            }
            memId = 'jsr_' + (jsrOptions.id !== undefined ? jsrOptions.id + '_' : 'gbl_');
        }
        

        jsrWindow_ = $(jsrHtml);
        icon_ = $(jsrIconHtml);

        // add window and icon to the dom
        $('body')
            .append(jsrWindow_)
            .append(icon_);

        histSelect_ = $('#historySelect').on('change', cmdSelected),
        cmdSelect_ = $('#jsr_cmd_list').on('change', cmdSelected),
        traceTarget_ = jsrWindow_.find('#jsr_cons_out'),
        consIn_ = jsrWindow_.find('#jsr_cons_in');

        // to avoid using own error trapping
        Function.prototype.oCall = origFuncCall;
        Function.prototype.oApply = origFuncApply;

        initCfg();
        initCommands();
        initWindowDrag();
        initResizeDrag();

        jsrWindow_.css( {
            top: cfg.winTop,
            left: cfg.winLeft,
            width: cfg.winW,
            height: cfg.winH,
            display: cfg.display
        });

        icon_.add('#jsr_close').click(function(event) {
            setVis(!isVisible());
        });

        consIn_.keydown(function (e) {

            var val,
                vArgs,
                cmdIdx,
                res,
                this_ = $(this);

            // a command is listening to keyboard input,
            // when passed the event, it will return true if should continue listening
            if (cmdKeyListener) {
                if (!cmdKeyListener(e)) {
                    cmdKeyListener = null;
                }
                return;
            } else if (e.keyCode === 13) {

                val = this_.val().trim();

                if (val === '') {
                    return;
                }

                this_.val('');
                traceInput(val);
                addToHist(val);
                histCycleIdx = NaN;

                vArgs = val.split(' ');
                cmdIdx = cmdNames.indexOf(vArgs[0]);

                if (catchingErrors) {
                    if (cmdIdx > -1) {
                        fn = cmds[cmdIdx];
                        res = fn.oApply(window, vArgs.slice(1));
                        if (res) {
                            scrollMgr.refresh(true);
                        }
                    } else {
                        res = eval.oCall(window, val);
                        if (res !== api || val === 'jsr') {
                            trace(res);
                        }
                        scrollMgr.refresh(true);
                    }
                    
                } else {
                    try {
                        if (cmdIdx > -1) {
                            fn = cmds[cmdIdx];
                            res = fn.oApply(window, vArgs.slice(1));
                            if (res) {
                                scrollMgr.refresh(true);
                            }
                        } else {
                            res = eval.oCall(window, val);
                            if (res !== api || val === 'jsr') {
                                trace(res);
                                // traceChain(res);
                            }
                            scrollMgr.refresh(true);
                        }
                    } catch (x) {
                        logError(x);
                        throw x;
                    }
                }
            } else if (e.keyCode === 40) {
                // down arrow was pressed, cycle through history
                if (isNaN(histCycleIdx)) {
                    return;
                }
                if (histCycleIdx !== cfg.histMemIdx) {
                    histCycleIdx = ++histCycleIdx % cfg.histMax;
                    this_.val(hist(histCycleIdx));
                } else {
                    histCycleIdx = NaN;
                    // reload previous input
                    this_.val(this_.data('prevInput') || '');
                }
           } else if (e.keyCode === 38) {
                // up arrow was pressed, cycle through history
                // before hist cycle, always at NaN
                if (isNaN(histCycleIdx)) {
                    histCycleIdx = cfg.histMemIdx;
                    if (hist.has(histCycleIdx)) {
                        // save anything typed for later
                        this_.data('prevInput', this_.val());
                        this_.val(hist.had());
                    } else {
                        // have no history
                        histCycleIdx = NaN;
                    }
                } else {
                    histCycleIdx = histCycleIdx === 0 ? cfg.histMax - 1 : histCycleIdx - 1;
                    if (hist.has(histCycleIdx)) {
                        this_.val(hist.had());
                    } else {
                        // reach the end of the history, so keep it where it was
                        histCycleIdx = ++histCycleIdx % cfg.histMax;
                    }
                }
            }
        });

        if (jsrOptions.useiScroll && window.iScroll) {

            scrollMgr = (function(){
                var scroller = new iScroll('jsr_scroller',
                    {
                        hScrollbar: false,
                        vScrollbar: true,
                        checkDOMChanges: true,
                        useTransform: false,
                        bounce: false
                    }),
                    api = {
                        bottomNoted: false,
                        notePos: function jsrNoteIScrollPosition() {
                            bottomNoted = scroller.y === scroller.maxScrollY;
                        },
                        refresh: function jsrRefreshIScroll(toBottom, duration) {
                            scroller.refresh();
                            // keep at bottom if already there or if should be forced to the bottom
                            // (for instance, if latest content traced was input from console)
                            if (scroller.maxScrollY < 0 && (bottomNoted || toBottom)) {
                                // if duration was supplied, replace 0 with undefined (no scroll animation)
                                // keep otherwise
                                if (!isNaN(duration)) {
                                    duration = duration === 0 ? undefined : duration;
                                } else {
                                    // default scroll duration is 0.2 seconds
                                    duration = 200;
                                }
                                scroller.scrollTo(0, scroller.maxScrollY, duration);
                            }
                            bottomNoted = false;
                        },
                        scrollTo: function(x,y,dur,rel) { scroller.scrollTo(x,y,dur,rel); },
                        getViewHeight: function() { return scroller.wrapperH; }
                    };
                return api;
            })();
        } else {
            scrollMgr = (function(){
                var scrollElm = document.getElementById('jsr_scroller'),
                    scrollAnim = (function jsrScrollAnimMgr() {
                        var intervalY = null,
                            intervalBoth = null,
                            startTime = -1,
                            dur = -1,
                            fromTop = -1,
                            toTop = -1,
                            chgTop = -1,
                            fromLeft = -1,
                            toLeft = -1,
                            chgLeft = -1,
                            updateY = function() {
                                var elapsed = Date.now() - start;
                                if (elapsed >= dur) {
                                    scrollElm.scrollTop = toTop;
                                    stopY();
                                } else {
                                    scrollElm.scrollTop = easeOutCirc(elapsed, fromTop, chgTop, dur);
                                }
                            },
                            updateBoth = function() {
                                console.log('update both: ' + (Date.now() - start) / dur);
                                var elapsed = Date.now() - start;
                                if (elapsed >= dur) {
                                    scrollElm.scrollTop = toTop;
                                    scrollElm.scrollLeft = toLeft;
                                    stopBoth();
                                } else {
                                    scrollElm.scrollTop = easeOutCirc(elapsed, fromTop, chgTop, dur);
                                    scrollElm.scrollLeft = easeOutCirc(elapsed, fromLeft, chgLeft, dur);
                                }
                            },
                            easeOutCirc = function(time, begin, chg, duration) {
                                return chg * Math.sqrt(1 - (time = time / duration - 1) * time) + begin;
                            },
                            stopY = function() {
                                if (intervalY) {
                                    clearInterval(intervalY);
                                    intervalY = null;
                                }
                            },
                            stopBoth = function() {
                                if (intervalBoth) {
                                    clearInterval(intervalBoth);
                                    intervalBoth = null;
                                }
                            },
                            go = function(scrollTo, duration) {
                                fromTop = scrollElm.scrollTop;
                                toTop = scrollTo;
                                chgTop = toTop - fromTop;
                                dur = duration;
                                start = Date.now();
                                stopBoth();
                                if (!intervalY) {
                                    intervalY = setInterval(updateY, 33);
                                }
                            },
                            // parameter order is set to match iScroll's scrollTo()
                            scrollTo = function(left, top, duration, isRelative) {
                                console.log('scroll to');
                                fromTop = scrollElm.scrollTop;
                                fromLeft = scrollElm.scrollLeft;
                                if (isRelative) {
                                    toTop = fromTop + top;
                                    chgTop = top;
                                    toLeft = fromLeft + left;
                                    chgLeft = left;
                                } else {
                                    toTop = top;
                                    chgTop = top - scrollElm.scrollTop;
                                    toLeft = left;
                                    chgLeft = left - scrollElm.scrollLeft;
                                }
                                dur = duration;
                                start = Date.now();
                                stopY();
                                if (!intervalBoth) {
                                    intervalBoth = setInterval(updateBoth, 33);
                                }
                            },
                            getViewHeight = function() {
                                return scrollElm.clientHeight;
                            };
                        return {
                            stop: stop,
                            go: go,
                            scrollTo: scrollTo,
                            getViewHeight: getViewHeight
                        };
                    })(),
                    api = {
                        bottomNoted: false,
                        notePos: function jsrNoteScrollPosition() {
                            bottomNoted = scrollElm.scrollHeight - scrollElm.scrollTop - scrollElm.clientHeight === 0;
                        },
                        refresh: function jsrRefreshScroll(toBottom, duration) {
                            // keep at bottom if already there or if should be forced to the bottom
                            // (for instance, if latest content traced was input from console)
                            if (bottomNoted || toBottom) {
                                // if duration is 0 then just set scroll position
                                if (duration === 0) {
                                    scrollAnim.stop();
                                    scrollElm.scrollTop = scrollElm.scrollHeight - scrollElm.clientHeight;
                                } else {
                                    duration = !isNaN(duration) ? duration : 200;
                                    scrollAnim.go(scrollElm.scrollHeight - scrollElm.clientHeight, duration);
                                }
                            }
                        },
                        scrollTo: scrollAnim.scrollTo,
                        getViewHeight: scrollAnim.getViewHeight
                    };
                scrollElm.style.overflow = 'auto';
                return api;
            })();
        }

        // if on the iPad, do fixed position hack:
        if (isiPad) {
            $(window).on('scroll', function jsrFixPosHack(evt){
                var top = document.documentElement.scrollTop || document.body.scrollTop,
                    left = document.documentElement.scrollLeft || document.body.scrollLeft;
                jsrWindow_.css('margin-top', top + 'px').css('margin-left', left + 'px');
            });
        } else {
            // reduce opacity when scrolling
            // (doesn't work on iPad bc infrequent scroll event)
            var timeoutId = NaN;
            $(window).on('scroll', function jsrOnScroll(evt) {
                var thisTimeoutId = NaN,
                    toFn = function jsrOnScrollTimeout() {
                        // if there wasn't another scroll event b4 timeout firing
                        if (timeoutId === thisTimeoutId) {
                            jsrWindow_.css('opacity', 1);
                            timeoutId = NaN;
                        }
                    };
                thisTimeoutId = setTimeout(toFn, 500);
                timeoutId = thisTimeoutId;
                jsrWindow_.css('opacity', 0.4);
            });
        }

        trace('jsr instance id: ' + jsrOptions.id);
        trace('jsr local storage id: ' + memId);
        trace('jsr config:');
        traceExpanded(cfg);

        api =  function jsr() { return trace.apply(null, arguments); };
        api.trace = trace;
        api.deep = function jsrDeep(data) { traceExpanded(data); };
        api.errors = catchErrors;
        api.seeStackClone = seeStackClone;
        api.addCmd = addCmd;
        api.addSnippet = addSnippet;
        api.seeCfgClone = seeCfgClone;
        api.setCfg = setCfg;
        api.clearCfg = clearCfg;
        api.vis = cmd_vis;
        api.options = jsrOptions;
        
        window.jsr = api;
    })();
    return api;
})();



#jsr *                          {
                                -webkit-box-sizing: border-box; box-sizing: border-box; vertical-align: text-top; /* natural box model */
                                -webkit-touch-callout: none; /* prevent callout to copy image, etc when tap to hold */
                                -webkit-text-size-adjust: none; /* prevent webkit from resizing text to fit */
                                -webkit-tap-highlight-color: rgba(0,0,0,0) !important; /* make transparent link selection, adjust opacity 0 to 1.0 */
                                -webkit-user-select: none; /* prevent copy paste, to allow, change 'none' to 'text' */
                                }

#jsr a                          { text-decoration:none; border:none; }

#jsr a.active                   { color:#e55569; }

#jsr a:hover                    { color:#00e;  }

#jsr_icon                       {
                                position: fixed;
                                bottom: 12px;
                                right: 12px;
                                width: 50px;
                                height: 30px;
                                display:block;
                                overflow:hidden;
                                background-color:transparent;
                                background-image:url('jsr_sprite.png');
                                background-repeat:no-repeat;
                                background-position: 0 -40px;
                                -webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.5);
                                box-shadow:0 0 3px rgba(0, 0, 0, 0.5);
                                }

#jsr                            {
                                font-size:12px;
                                width:600px;
                                height:600px;
                                -webkit-overflow-scrolling:touch;
                                border-top-left-radius:3px;
                                border-top-right-radius:3px;
                                border:1px solid #000;
                                background-color: #333;
                                position: fixed;
                                z-index: 9999;
                                font-family: "Droid Sans Mono", Menlo, "Consolas", Monaco, "Courier", "Courier New", monospace;
                                color: #000;
                                -webkit-font-smoothing: subpixel-antialiased;
                                }

#jsr.iPad                       { position: absolute; }

#jsr_chrome_top                 {
                                display: block;
                                position: absolute;
                                z-index: 9999;
                                top: 0;
                                height: 23px;
                                width: 100%;
                                border-top-left-radius:2px;
                                border-top-right-radius:2px;
                                background: -webkit-gradient(linear, left top, left bottom, 
                                                             color-stop(100%,#868686), 
                                                             color-stop(0%,  #9a9a9a));
                                background: -webkit-linear-gradient (top, #868686 100%, #9a9a9a 0%);
                                }

#jsr_close                      {
                                background-color:transparent;
                                background-image:url('jsr_sprite.png');
                                background-repeat:no-repeat;
                                background-position: 0px -20px;
                                width:10px;
                                height:10px;
                                margin: 7px 0 0 11px;
                                }


#jsr_scroller_wrap              {
                                height: 100%; 
                                width: auto; 
                                padding: 24px 0 34px 0;
                                }

#jsr_scroller                   {
                                -webkit-overflow-scrolling:touch;
                                position: relative;
                                top: 0;
                                height: 100%;
                                background-color: #f8f8f8;
                                color: #121212;
                                }


.jsrSprite                      {
                                display:block;
                                overflow:hidden;
                                background-color:transparent;
                                background-image:url('jsr_sprite.png');
                                background-repeat:no-repeat;
                                }

#jsr_cons_out *                 {  -webkit-user-select: text;  }

#jsr_cons_out                   {
                                white-space: pre;
                                padding: 10px 0 10px 0;
                                margin-left: 40px;
                                background-color: #f8f8f8;
                                }

#jsr_cons_out a                 {  color: #a840a7;  }



 
/** each console output is put into a div **/

.jsr_co_line                    {
                                position: relative;
                                top: 0;
                                left: 0;
                                border-top: 1px solid  white; 
                                border-bottom: 1px solid  #d9d9d9;
                                border-left: 1px solid #d9d9d9;
                                padding: 7px 0 7px 20px;
                                }

.jsr_co_line_input_boundary     {
                                position: absolute;
                                top: 0;
                                left: 0;
                                width: 100%;
                                border-top: 4px solid  #1c7ce3;
                                }

.jsr_co_line_input              {
                                position: relative;
                                top: 0;
                                left: 0;
                                border-bottom: 1px solid  #d9d9d9;
                                border-left: 1px solid #d9d9d9;
                                padding: 7px 0 7px 20px;
                                }

.jsr_co_line_error_boundary     {
                                position: absolute;
                                top: 0;
                                left: 0;
                                width: 100%;
                                border-top: 4px solid  #c00;
                                }

.jsr_co_line_error              {
                                color: #c00;
                                position: relative;
                                top: 0;
                                left: 0;
                                border-bottom: 1px solid  #d9d9d9;
                                border-left: 1px solid #c00;
                                padding: 7px 0 7px 20px;
                                }

.jsr_co_errorInfo               {
                                vertical-align: baseline;
                                line-height: 1.5em;
                                padding-top: 4px;
                                margin: 0;
                                color: #c00;
                                }

.jsr_co_basic                   {
                                vertical-align: baseline;
                                line-height: 1.5em;
                                padding-top: 4px;
                                margin: 0;
                                }


.jsr_consExpander               {
                                display: block;
                                position: absolute;
                                top: 0;
                                left: 0;
                                height: 100%;
                                width: 40px;
                                float: left;
                                padding: 1px 0 2px 0;
                                background-clip: content-box;
                                }

.jsr_co_propLvl0                {  left: -41px; background-color: rgba(0,0,0,0.05); z-index: 0;  }
.jsr_co_propLvl1                {  left: -65px; background-color: rgba(0,0,0,0.05); z-index: 1;  }
.jsr_co_propLvl2                {  left: -89px; background-color: rgba(0,0,0,0.05); z-index: 2;  }
.jsr_co_propLvl3                {  left: -113px; background-color: rgba(0,0,0,0.05); z-index: 3;  }
.jsr_co_propLvl4                {  left: -137px; background-color: rgba(0,0,0,0.05); z-index: 4;  }
.jsr_co_propLvl5                {  left: -161px; background-color: rgba(0,0,0,0.05); z-index: 5;  }
.jsr_co_propLvl6                {  left: -185px; background-color: rgba(0,0,0,0.05); z-index: 6;  }
.jsr_co_propLvl7                {  left: -209px; background-color: rgba(0,0,0,0.05); z-index: 7;  }

.jsr_co_zLvl0                   {  z-index: 0;  position: relative;  }
.jsr_co_zLvl1                   {  z-index: 1;  position: relative;  }
.jsr_co_zLvl2                   {  z-index: 2;  position: relative;  }
.jsr_co_zLvl3                   {  z-index: 3;  position: relative;  }
.jsr_co_zLvl4                   {  z-index: 4;  position: relative;  }
.jsr_co_zLvl5                   {  z-index: 5;  position: relative;  }
.jsr_co_zLvl6                   {  z-index: 6;  position: relative;  }
.jsr_co_zLvl7                   {  z-index: 7;  position: relative;  }

.jsr_co_propList                {
                                list-style-type: none;
                                padding-left: 0;
                                margin: 1px 0 0 0;
                                }

.jsr_co_oProp, .jsr_co_fnBody   {
                                padding: 2px 4px 4px 20px;
                                margin-left: 3px;
                                color: #D14;
                                border-left: 1px solid #e6e6e6;
                                vertical-align: baseline;
                                position: relative;
                                }

.jsr_co_fnBody                  {
                                border-left: 6px solid #999;
                                }   

.jsr_cons_inVal                 { 
                                display: block;
                                color: #0228af;  
                                vertical-align: baseline;
                                padding: 0 0 0 15px;
                                margin-left: -15px;
                                }

.jsr_cons_inIcon                {
                                display:block;
                                overflow:hidden;
                                width: 5px;
                                height: 9px;
                                float: left;
                                position: relative;
                                margin: 5px 0 10px -13px;
                                background-color:transparent;
                                background-image:url('jsr_sprite.png');
                                background-repeat:no-repeat;
                                background-position: -10px 0;
                                }

.jsr_cons_errorIcon     
                                {
                                display: block;
                                width: 14px;
                                height: 15px;
                                position: absolute;
                                left: 0;
                                top: 0;
                                margin: 12px 0 0 3px;
                                overflow:hidden;
                                background-color:transparent;
                                background-image:url('jsr_sprite.png');
                                background-repeat:no-repeat;
                                background-position: -60px -40px;
                                }


#jsr_cons_out li span           {  vertical-align: baseline;  }

.jsr_propName                   {  color: #08c;  }


#jsr_cons_in_chrome             {
                                position: absolute;
                                bottom: 0;
                                width: 100%;
                                border-top: 1px solid #333;
                                height: 34px;
                                background: -webkit-gradient(linear, left top, left bottom, 
                                                             color-stop(100%,#868686), 
                                                             color-stop(0%,  #9a9a9a));
                                background: -webkit-linear-gradient (top, #868686 100%, #9a9a9a 0%);
                                }

#jsr_cons_ps1                   {
                                width: 30px;
                                height: 24px;
                                position: absolute;
                                top: 0px;
                                margin-left: -2px;
                                border-radius: 3px;
                                border: 1px solid #626262;
                                background: url('jsr_sprite.png'),
                                            -webkit-gradient(linear, left top, left bottom, 
                                                             color-stop(100%,#cccccc), 
                                                             color-stop(0%,  #fcfcfc));
                                background: url('jsr_sprite.png'),
                                            -webkit-linear-gradient (top, #cccccc 100%, #fcfcfc 0%);

                                background-position: -110px 0;
                                }

#jsr_cmd_list                   {
                                position:absolute;
                                margin:0 0 0 0px;
                                left: -4px;
                                bottom: 0px;
                                z-index:100;
                                -webkit-appearance:none;
                                -webkit-user-select:none;
                                background-attachment:scroll;
                                background-clip:border-box;
                                background-color:transparent;
                                background-image:none;
                                background-origin:padding-box;
                                border-radius: 0px;
                                border: 0px;
                                outline-color:transparent;
                                outline-style:none;
                                outline-width:0px;
                                text-align:center;
                                color:rgba(0,0,0,0);
                                width:45px;
                                height:33px;
                                /*border:1px solid #0f0;*/ 
                                }

#jsr_cons_in_wrap               {
                                position: relative;
                                height: 34px;
                                width: 100%;
                                padding: 0 67px 0 10px;
                                margin-top: 5px;
                                }

#jsr_cons_in                    {
                                color: #000;
                                width: 100%;
                                height: 24px;
                                font-family: Menlo, Monaco, "Consolas", "Courier", "Courier New", monospace;
                                -webkit-user-select: text;
                                border: 1px solid #999;
                                padding: 0 0 0 29px;
                                border-radius: 5px;
                                vertical-align: middle;
                                }


#historySelect                  {
                                position:absolute;
                                z-index:100;
                                -webkit-appearance:none;
                                -webkit-user-select:none;
                                background-attachment:scroll;
                                background-clip:border-box;
                                background-color:transparent;
                                background-image:none;
                                background-origin:padding-box;
                                border-radius: 0px;
                                border: 0px;
                                outline-color:transparent;
                                outline-style:none;
                                outline-width:0px;
                                margin:0 0 0 0px;
                                right: 22px;
                                bottom: 0px;
                                text-align:center;
                                color:rgba(0,0,0,0);
                                width:45px;
                                height:33px;
                                /*border:1px solid #0f0;*/ 
                                }

#historySelectGraphic           {
                                position: absolute;
                                right: 33px;
                                bottom: 8px;
                                background-color:transparent;
                                background-image:url('jsr_sprite.png');
                                background-repeat:no-repeat;
                                background-position: -60px 0;
                                width:23px;
                                height:15px;
                                }   

#resizeHandle                   {   
                                cursor: pointer;
                                position: absolute;
                                right: -9px;
                                bottom: -9px;
                                padding: 14px;
                                width:9px;
                                height:9px;
                                /*border:1px solid #0f0;*/
                                }

#resizeHandleIcon               {
                                display: block;
                                background-color:transparent;
                                background-image:url('jsr_sprite.png');
                                background-repeat:no-repeat;
                                background-position: -90px 0;
                                width:11px;
                                height:11px;
                                margin: -8px 0 0 -8px;
                                /*border:1px solid #0ff;*/
                                }

The jsr window can be dragged around by clicking the top grey bar. It can be resized by clicking the lower right corner of the window and dragging. It can be turned off by clicking the 'x' in the upper left corner. The window can be shown or hidden by clicking the fixed-position button on the lower left corner of the page.

  • Draggable
  • Resizeable
  • Snippets for faster command entry
  • Command history
  • iScroll can be used
  • Collapse / expand Objects, Functions and Arrays to see their properties
  • Function bodies are syntax-highlighted

jsr has error logging functionality that can be enabled by entering the command .errs true into the console or calling jsr.errors(true). This can be useful when browser-based logging and diagnostic tools, or even third-party native based tools, are unavailable.

jsr error logging will log top-level errors (window.onerror) and will attempt to log the execution stack. To see some logging in action:

jsr.errors(true);
jsr('Errors enabled, now cause an error').notExist();

In some cases third-party mobile debugging apps can't be used. This can happen, for instance, if creating content specifically for a third-party platform, as is often the case with closed loop marketing.

The dark side of the Force is a pathway to many abilities some consider to be unnatural.

Supreme Chancellor, Star Wars: Episode III

As I understand it, execution transpires by way of calls to Function.call and Function.apply. When logging is enabled, these two methods are wrapped by functions that note the execution context and then pass the call on to the original Function.call or Function.apply method. If the calls to the original methods cause an error, the error and execution context are logged to the jsr console.

The original Function.call and Function.apply methods are saved in Function.oCall and Function.oApply, which can be seen via jsr.deep(Function).

To be honest, the way this currently works is a bit spotty. But, you do get a lot more information when errors are encountered than when relying only on window.onerror.

The wrapper functions look like this:

function funcCallWrapper(thisArg) {
    var res;
    try {
        var toStr = fnToShortStr(this),
            callIdx = callStack.length,
            callInfo = {idx: callStack.length, fn: this},
            args;

        if (arguments.length > 1) {
            args = Array.prototype.slice.oApply(arguments, [1]);
            callInfo.args = args;
        }
        Array.prototype.unshift.oCall(callStack, callInfo);
        res = this.oApply(thisArg, args);
        Array.prototype.shift.oCall(callStack);
        // return res;
    } catch (x) {
        logError(x, Array.prototype.shift.oCall(callStack));
        throw x;
    }
    // not reached if an error was encountered bc throws x
    return res;
}

function funcApplyWrapper(thisArg, args) {
    var res;
    try {
        var toStr = fnToShortStr(this),
            callIdx = callStack.length;

        Array.prototype.unshift.oCall(callStack, {
            idx: callStack.length, 
            fn: this, 
            args: args
        });
        res = this.oApply(thisArg, args);
        Array.prototype.shift.oCall(callStack);
        // return res;
    } catch (x) {
        logError(x, Array.prototype.shift.oCall(callStack));
        throw x;
    }
    // not reached if an error was encountered bc throws x
    return res;
}

jsr has several built-in commands, and commands can be added by calling jsr.addCmd(...). The following built-in commands are available.

Size

Description

Adjusts the width and height of the console window.

Aliases .sz
.size
Usage $ .sz

Enters an interactive mode; the console window responds to arrow keys to adjust width and height of the window. Pressing the <shift> key increases the change, and pressing the <return> key exits the command.

$ .size [w] [,h] Adjusts the width and height of the console window by w and h, respectively.
Parameters none Enters an interactive mode.
w

Adjusts the width of the window.

h

Adjusts the height of the window.

Parameter Types Number Redefines the dimension of the window.
"String" Causes a relative adjustment of the dimension of the window.
Examples $ .sz 800 400 Sets the console window width to 800 and height to 400.
$ .sz "-100" "50" Decreases the console window's width by 100 and increases the height by 50.
$ .size "100" 600 Increases the console window's width by 100 and sets the height to 600.

Move

Description Adjusts the position of the console window in the view-port.
Aliases .mv
.move
Usage $ .mv

Enters an interactive mode; the console window responds to the arrow keys and adjusts the position of the window. Pressing the <shift> key increases the change, and pressing the <return> key exits the command.

$ .mv [x] [,y] Adjusts the horizontal and vertical positions of the console window by x and y, respectively.
Parameters none Enters an interactive mode.
x Adjusts the horizontal position of the window.
y Adjusts the vertical of the window.
Parameter Types Number Redefines the position of the window.
"String" Causes a relative adjustment of the position of the window.
Examples $ .mv 800 400 Sets the position of the console window to (800,400).
$ .mv "-100" "50" Decreases the console window's x position by 100 and increases the y position by 50.
$ .move "100" 600 Increases the console window's x position by 100 and sets the y position to 600.

Scroll

Description Adjusts the scroll position of the output in the console window.
Aliases .scroll
...
Usage $ .scroll

Enters an interactive mode; the console window responds to arrow keys to adjust the scroll position of the output. Pressing the <shift> key increases the change, and pressing the <return> key exits the command.

Parameters none Enters an interactive mode.

Error logging

Description Turns error logging on or off.
Aliases .errs
Usage $ .errs Prints the current state of error logging to the console.
$ .errs [value] Enables or disables error logging.
Parameters none Prints the current state of error logging to the console.
true | false Enables or disables error logging.
Examples $ .errs true Enables error logging.
$ .errs false Disbales error logging.

Clear screen

Description Clears the console output.
Aliases .cls
Usage $ .cls

Visible

Description Toggles the visibility of the jsr console window.
Aliases .vis
Usage $ .vis

Console input is retained in a rolling history and is persisted across page refreshes (via localStorage). You can cycle through the history with the up and down arrow keys. The length of the history is defined in the jsr config histMax setting. This can also be adjusted via the jsr initialization options.

The console has a handful of config settings. These can be updated via jsr.setCfg(...). A copy of the current settings can be retrieved by calling jsr.seeCfgClone().

These values are maintained in localStorage and are loaded when the console is initialized. If the values are not found in localStorage the defaults are used.

Custom properties and values can be added to the config settings by calling jsr.setCfg(...). These new settings will be maintained in localStorage and retrieved at initilalization, as well.

Name Type Default Description
winTop <length> 100px The y position of the console window.
winLeft <length> 100px The x position of the console window.
winW <length> 600px The width of the console window.
winH <length> 600px The height of the console window.
catchErrors true | false false Whether or not error logging is enabled.
histMax int > 0 100 Number of input commands to retain in the console history.
histMemIdx int -1 Current position in history list.
display 'block' | 'none' 'block' Whether or not the console window is visible.

The console has has a handful of init settings. These must be defined through a window.jsrOptions variable. These settings can be accessed via the jsr.options variable and can referenced by commands that are added to the console (see jsr.addCmd(...)).

If the value of the window.jsrOptions variable is a String, this value will be used as the id init setting. If the value of the window.jsrOptions variable is an Object, every property on this object will be available via the jsr.options variable, and, if defined, the id and useiScroll properties will be used in the place of the defaults.

Name Type Default Description
id String "gbl_" Id used to prevent naming conflicts in localStorage.
useiScroll true | false true

Whether or not iScroll should be used (ignored if iScroll is undefined).

The jsr JavaScript object has the following methods available:

Logs expressions to the JSR console window's output. A single trace statement can support a single or multiple arguments and will function differently based on the number of arguments.

Parameters
... arguments

If an Object, Function, or Array is the only argument, it is logged as an expandable log item. Expandable log items can be inspected by clicking either the log item itself or the gray bar in the left column of the window. Exapnding the log item will show the properties and function body (if it is a function) of the argument it represents.

If a primitive is the single argument, its value is logged.

If there are multiple arguments, String representations of the arguments are concatented with a ' ' (space) and logged.

Returns

Returns the jsr object in all cases.

Examples
jsr.trace("hello world!"); 
jsr.trace(null); 
jsr.trace(window); 
jsr.trace(jsr); 
jsr.trace(jsr, window, 999);

Logs an expression to the JSR console window's output. If the expression is a Function, Object or Array, the value is logged as an interactive element (see jsr.trace(...);) and is expanded to show the properties of the value and the function body (if it's a function).

Parameters
arg

If arg is an Object, Function or Array, it is logged as an expandable log item and expanded.

If arg is a primitive, its value is logged.

Returns
Returns the jsr object in all cases.

Examples
jsr.deep(window.location); 
jsr.deep("primitive_value"); 
jsr.deep(jsr); 

Either enables / disables error logging or retrives a boolean indicating whether or not error logging is enabled or not.

Parameters
value    optional

A If value is present, error logging will be turned on or off, depending on whether value is truthy or not.

B If a value is not present, a boolean indicating whether or not error logging is current enabled is returned.

Returns

A Returns the jsr object.

B Returns true if error logging is currently enabled and false if it's currently disabled.

Examples
jsr.errors(); 
jsr.errors(true); 
jsr.errors(false); 

Returns a clone of the current record of the execution stack. If error logging is not enabled, this will genererally be an empty Array. If error logging is enabled, this will be an Array of objects with the following structure:

{
    idx: positive_int,
    fn: some_function,
    args: the_calls_args
} 
Parameters
none
Returns
A clone of the record of the execution stack.
Examples
jsr.seeStackClone(); 

Adds a new command to the console.

Parameters
alias
The input string that will be match the new command.
fn
The function called when the alias is entered into the console.
addSnippet    optional
If true, the alias is added to the snippet menu on the console window.
Returns
Returns the jsr object.
Examples
var fn = function helloCmd() { 
    alert("hello world!"); 
};
jsr.addCmd("hi", fn); 
var fn = function wiseCmd() { 
    alert("?"); // confidential 
};
jsr.addCmd("wise", fn, true); 

Adds a snippet to the snippet menu on the console window.

Parameters
value
The text to add to the snipeet menu.
Returns
Returns the jsr object.
Examples
jsr.addSnippet("setTimeout(function(){"); 

Returns a clone of the config settings object.

Parameters
none
Returns
A clone of the current config settings object.
Examples
jsr.deep(
    jsr.seeCfgClone()
); 

Updates the current config settings and saves the new configuration to localStorage. Configuration changes are persistent across page refreshes as long as the same ID is used when the console is initialized.

Custom properties and values can be added to the config settings

Parameters
a
If a is an Object, every property on a will be updated or added to the config settings.
If a is a String, a corresponding property will be updated or added to the config settings.
b    optional
If a is a String, the value of the corresponding property will be set to b.
Returns
The jsr object.
Examples
jsr.setCfg({
    customProp1: "rad",
    customProp2: [0,1,2]   
}); 
jsr.setCfg("customProp1", true); 

Either shows / hides the console window or retrives a boolean indicating whether the console window is visible or not.

Parameters
value    optional

A If value is present, the console window visibility will be turned on or off, depending on whether value is truthy or not.

B If a value is not present, a boolean indicating whether or not the console window is current visible.

Returns

A Returns the jsr object.

B Returns true if the console window is visible and false if it's not.

Examples
jsr.vis(); 
jsr.vis(true); 
jsr.vis(false);