diff --git a/debugger.js b/debugger.js index 2c440f3..06c2a6d 100644 --- a/debugger.js +++ b/debugger.js @@ -4,14 +4,102 @@ var file_get_contents = function(f,mode){return (!file_exists(f))? '' : require( var get_constr = function(v){ return(v===null)?"[object Null]":Object.prototype.toString.call(v); }; // var fnprefix = (["FUNCTION"].concat(process.hrtime()).concat(process.hrtime())).join('.'); // ONLY WORKS IN LATER Vs var fnprefix = 'TYPE_FUNC_'+(new Date().getTime()); + +var util = require("util"); var stringify = require('./stringify.js'); -var jsencr = function(o){ var e = []; return stringify(o, function(k,v){ - if(typeof(v)==='function') return fnprefix+v.toString(); - if(typeof(v)!=='object' || v===null) return v; - for(var i in e){ if(e[i]===v){ return "Circular"; }}; - e.push(v); return v; -}); }; +function objGetPropertyDescriptor(obj, key) { + while(true) { + var desc = Object.getOwnPropertyDescriptor(obj, key); + if(desc) return desc; + obj = Object.getPrototypeOf(obj); + if(obj === Object) return null; + } +} + +var jsencr = function(o, rawRoot) { + function objDescription(o) { + var value = {}; + var id = objCache.register(o); + value.type = "object"; + value.typename = "Object"; + if(o.constructor && o.constructor.name) + value.typename = o.constructor.name; + value.str = "[" + value.typename + "]"; + value.objid = id; + value.keys = []; + value.attribs = []; // each item is a 2-tuple. _[0] says whether this is static. if static, _[1] is the value. + for(var key in o) { + // Add all keys which are strings or int. Otherwise, don't for now for simplification. + if(!util.isString(key) && !util.isNumber(key)) continue; + value.keys.push(key); + var prop = objGetPropertyDescriptor(o, key); + if(!prop || !prop.get) + value.attribs.push([true, o[key]]); + else + value.attribs.push([false, undefined]); + } + return value; + } + function funcDesc(f) { + var value = {}; + value.type = "function"; + value.typename = "Function"; + value.name = f.name; + if(value.name) value.str = "[Function: " + f.name + "]"; + else value.str = "[Function]"; + return value; + } + var e = []; + return stringify(o, function(k,v){ + if(typeof(v)==='function') return funcDesc(o); + if(typeof(v)!=='object' || v===null) return v; + for(var i in e){ if(e[i]===v){ return "Circular"; }}; + e.push(v); + if(util.isArray(v)) return v; + if(v === o && rawRoot) return v; + return objDescription(v); + }); +}; + +var assert = require("assert"); + +function getRandomId() { + return Math.floor((new Date()).valueOf() + new Date(2000,0,0).valueOf()*Math.random()); +} + +function createObjCache() { + var objToId = new WeakMap(); + var idToObj = new Map(); + + return { + register: register, + get: get + }; + + function register(obj) { + if(objToId.has(obj)) return objToId.get(obj); + var newId; + while(true) { + newId = getRandomId(); + if(!idToObj.has(newId)) break; + } + objToId.set(obj, newId); + idToObj.set(newId, obj); + return newId; + } + + function get(id) { + return idToObj.get(Number(id)); + } +} + +var objCache = createObjCache(); + +function clear() { + // reset obj cache + objCache = createObjCache(); +} var dbg = { @@ -41,7 +129,8 @@ var dbg = { broadcastSSE: function(t, a){ clearTimeout(dbg.pendingBroadcast); - var data = { t:t, a:a }; + a = Array.prototype.slice.call(a, 0);; // make it an array + var data = [t,a]; dbg.queued.push(data); var sendFn = function(){ @@ -151,9 +240,9 @@ var dbg = { r.type = (typeof(r.cnt)==='object' && r.cnt!==null) ? get_constr(r.cnt) : typeof(r.cnt); - }catch(e){ r.error=e.toString(); } + }catch(e){ r.error= "" + (e.stack || e); } - s.end(jsencr(r)); + s.end(jsencr(r, true)); } else if(typeof(post.getsug)==='string'){ try{ var r = jsencr(dbg.getsug(JSON.parse(post.getsug))); } @@ -161,6 +250,25 @@ var dbg = { s.writeHead(200, {'Content-type': dbg.mimes['txt'], 'Content-length': r.length}); s.end(r); + } else if(post.clear) { + clear(); + s.end(); + } else if(post.dynget) { + var objid = post.dynget; + var key = post.key; + var r = {error:false}; + + try{ + var obj = objCache.get(objid); + if(!obj) + r.error = "Object not found. Probably we have restarted the session."; + else + r.cnt = obj[key]; + } catch(e) { + r.error= "" + (e.stack || e); + } + + s.end(jsencr(r, true)); }else{ return dbg.serve500(s,'Command was not found'); } diff --git a/scripts/console.js b/scripts/console.js index 35539a1..4111cf0 100644 --- a/scripts/console.js +++ b/scripts/console.js @@ -14,42 +14,43 @@ $(document).ready(function(){ var encd = function(v){ return $('
').text(v).html();}; var decd = function(v){ return $('
').html(v).text();}; - window.clearConsole = function(){ c.val(''); return vw.html('');}; - - var treefiy_obj = function(o, n, c){ - var r = { data:""+n+"", state:!!c ? 'open' : 'closed', children:[] }; - for(var i in o){ - if(typeof(o[i])==='object' && o[i]!==null) - o[i] = treefiy_obj(o[i], i); - else{ - var val = encd(o[i]); - // if(val.length>100) val = ''+val.substr(0,100)+'... (length: '+val.length+')'; - o[i] = ""+i+""+val+''; - }; - r.children.push(o[i]); - }; - return r; - }; + window.encodeHTML = function(s) { + s = s.replace(/&/g, '&'); + s = s.replace(/'); + s = s.replace(/ /g, ' '); + s = s.replace(/\t/g, ' '); // not exactly... + return s; + } + window.clearConsole = function() { + $.ajax({url:'./', type:'POST', dataType:'text', data:{'clear':true}, complete:function(r) { + if(r.status!==200) return showAnError('Bad response from server ('+r.status+')'); + + c.val(''); + return vw.html(''); + }}); + }; window.focusLastMessage = function(){ vwscr.scrollTo(0,vw.height()); }; window.showAnError = function(err, type){ if(typeof(type)!=='string') type='error'; var resp = $('
').appendTo(vw); - resp.html(' W '+encd(err)); + resp.html(' W '+encodeHTML(err)); focusLastMessage(); }; window.showAResponse = function(r){ var resp = $('
').appendTo(vw); - var tpo = typeof(r.cnt); if(typeof(r.fnprefix)==='string') window.fnprefix = r.fnprefix; + var tpo = typeof(r.cnt); if(r.cnt===null) tpo = 'null'; switch(tpo){ case 'object': - createTreeFromObj({'[object Object]':r.cnt}, $('.autoexpand').is('.sel')).appendTo(resp); + createTreeFromObj(r.cnt, $('.autoexpand').is('.sel')).appendTo(resp); break; default: $(formatStaticValue(r.cnt,false)).appendTo(resp); diff --git a/scripts/localtree.js b/scripts/localtree.js index 21c38f5..5ca0d01 100644 --- a/scripts/localtree.js +++ b/scripts/localtree.js @@ -5,7 +5,6 @@ window.formatStaticValue = function(data, nobrk){ var encd = function(v){ return $('
').text(v).html();}; var fnprefix = typeof(window.fnprefix)=='string' ? window.fnprefix : '{'+(new Date().getTime()) + '#!@'; - if(type==='object') return false; if(data===null) type='null'; if(type==='string' && typeof fnprefix !== 'undefined' && data.indexOf(fnprefix+'function')===0 && data.lastIndexOf(fnprefix+'function')===0){ type='function'; @@ -21,50 +20,133 @@ window.formatStaticValue = function(data, nobrk){ return ''+(typeof(data)!=='boolean'?data:(!!data?'true':'false'))+''; case 'string': case 'function': - var d = encd(data.toString()); - if(!!!nobrk) d = d.replace(/\r\n/gmi,"
").replace(/\n/gmi,"
").replace(/\t/gmi,tabSpaces); + var d = encodeHTML(data.toString()); var r = '' + (d)+''; return (type=='string' ? '"'+r+'"' : r); + default: + return 'unknown '+type+''; }; }; -window.createTreeFromObj = function(obj,autoexpand){ +function appendAttrib(k, d, container, autoexpand) { + if(typeof(d)!=='object' || d===null){ + $('
'+k+''+formatStaticValue(d,false) +'
').appendTo(container); + }else if(d.type == "function"){ + $('
'+k+''+encodeHTML(d.str)+'
').appendTo(container); + }else{ + var li = $('').appendTo(container); + var hdr = $('
').appendTo(li); + var arrow = $('').appendTo(hdr); + var key = $('' + k +'').appendTo(hdr); + var descStr = d.str; + if(!descStr && d.constructor) descStr = "[" + d.constructor.name + "]"; + if(!descStr) descStr = ""; + var desc = $('' + encodeHTML(descStr) + ' ').appendTo(hdr); + + var expand = function(){ + var tgt = li.find('>.object'); + if(tgt.length && tgt.is(':visible')){ + tgt.hide(); + arrow.removeClass('arrow-expanded').addClass('arrow-collapsed').html('►'); + }else{ + if(!tgt.length) tgt = createTreeFromObj(d).appendTo(li); + tgt.show(); + arrow.removeClass('arrow-collapsed').addClass('arrow-expanded').html('▼'); + }; + if(typeof(doResizeWin)=='function') doResizeWin(); + }; + + arrow.click(function(){ if(!$('.dotstruct').is('.sel')) expand(); }); + hdr.click(function(){ if($('.dotstruct').is('.sel')) expand(); }); + if(!!autoexpand) expand(); + }; +} + + +window.createTreeFromDynObj = function(obj,autoexpand){ if(typeof(obj)!=='object' || null==obj) return false; - - var ul = $('
    '); - var keys = []; for(var i in obj) keys.push(i); if(typeof(obj.length)=='undefined') keys.sort(); - for(var i=0; i'+encodeHTML(obj.str)+''; + + var ul = $('
      '); - if(typeof(d)!=='object' || d===null){ - $('
    • '+k+' '+formatStaticValue(d,1) +'
    • ').appendTo(ul); - }else{ - var li = $('').appendTo(ul); + function appendAttribGeneric(k, attr) { + var li = $('
    • ').appendTo(ul); + + if(!attr[0]) { var hdr = $('
      ').appendTo(li); - var arrow = $('').appendTo(hdr); var key = $('' + k +'').appendTo(hdr) + var getCmd = $('<get>').appendTo(hdr); - var expand = function(){ - var tgt = li.find('>.object'); - if(tgt.length && tgt.is(':visible')){ - tgt.hide(); - arrow.removeClass('arrow-expanded').addClass('arrow-collapsed').html('►'); - }else{ - if(!tgt.length) tgt = createTreeFromObj(d).appendTo(li); - tgt.show(); - arrow.removeClass('arrow-collapsed').addClass('arrow-expanded').html('▼'); - }; - if(typeof(doResizeWin)=='function') doResizeWin(); - }; + function showError(msg) { + var resp = $('
      ').appendTo(hdr); + resp.html(' W ' + encodeHTML(msg)); + } - arrow.click(function(){ if(!$('.dotstruct').is('.sel')) expand(); }); - hdr.click(function(){ if($('.dotstruct').is('.sel')) expand(); }); - if(!!autoexpand) expand(); - }; + function getAttrib() { + getCmd.remove(); + var waitSpan = $("...").appendTo(hdr); + + $.ajax({url:'./', type:'POST', dataType:'text', data:{dynget:obj.objid, key:k}, complete:function(r){ + waitSpan.remove(); // remove old + + if(r.status!==200) { + showError("Bad server response " + r.status); + return; + } + + try{ var pa = JSON.parse(r.responseText); }catch(e){ + showError('Failed to parse response ('+e+')'); + return; + }; + + if(pa.error) { + showError(pa.error); + return; + } + + hdr.remove(); + appendAttrib(k, pa.cnt, li, true); + + // re-add get-cmd to new header + hdr = li.find(">div")[0]; + tgt = li.find(".header")[0]; + getCmd.text(""); + getCmd.appendTo(tgt); + getCmd.click(getAttrib); + }}); + }; + getCmd.click(getAttrib); + hdr.click(getAttrib); + return; + } - }(obj[keys[i]], keys[i])); + var d = attr[1]; + appendAttrib(k, d, li, autoexpand); + } + + for(var i=0; i'); + for(var key in obj) { + var li = $('
    • ').appendTo(ul); + appendAttrib(key, obj[key], li, autoexpand); + } + return ul; +} + +window.createTreeFromArray = window.createTreeFromRawObj; + +window.createTreeFromObj = function(obj, autoexpand) { + if(Array.isArray(obj)) return window.createTreeFromArray(obj, autoexpand); + return createTreeFromDynObj(obj, autoexpand); +} diff --git a/scripts/sse.js b/scripts/sse.js index 765f0b8..749c5d2 100644 --- a/scripts/sse.js +++ b/scripts/sse.js @@ -32,17 +32,17 @@ $(document).ready(function(){ setTimeout(function(){ catch(e){ var q = []}; for(var j=0; j