improved exploration support
authorAndrew Sutherland <asutherland@asutherland.org>
Thu, 15 Oct 2009 21:36:12 -0700
changeset 5 4678231abf9544d1386ded3b378dc9feba2f74de
parent 4 abe81cebc257b739d63556b9369bfba1b643187d
child 6 4008b41ff30e7788900fc05bcc49ac918d4fe0c9
push id2
push userbugmail@asutherland.org
push dateMon, 26 Oct 2009 10:02:58 +0000
improved exploration support
chrome/content/formatters.js
chrome/content/logsploder.xul
chrome/content/logui.js
chrome/content/logviewer.css
chrome/content/logviewer.xhtml
--- a/chrome/content/formatters.js
+++ b/chrome/content/formatters.js
@@ -1,10 +1,30 @@
 let FormatHelp = {
+  /**
+   * Create/wrap into a clicky node that shows an object explorer for the given
+   *  object when clicked.
+   */
+  makeDetailLink: function FormatHelp_makeDetailLink(textOrNodes, detailObj) {
+    let node = document.createElement("a");
+    if (typeof(textOrNodes) == "string") {
+      node.textContent = textOrNodes;
+    }
+    else {
+      for each (let [, childNode] in Iterator(textOrNodes)) {
+        node.appendChild(childNode);
+      }
+    }
 
+    node.onclick = function() {
+      LogUI.showDetail(detailObj, node);
+    };
+
+    return node;
+  }
 };
 
 let LogFormatters = {
   test: { // and subtest
     stringify: function format_test_stringify(obj) {
       return obj.type + ": " + obj.name + " " + obj.parameter;
     }
   },
@@ -31,23 +51,23 @@ let LogFormatters = {
     }
   },
 
   /* ************ _normalize_for_json stuff ************* */
 
   folder: {
     stringify: function format_folder_stringify(obj) {
       return "Folder: " + obj.name;
-    }
+    },
   },
 
   msgHdr: {
     stringify: function format_msgHdr_stringify(obj) {
       return "MsgHdr: " + obj.name;
-    }
+    },
   },
 
   // a single frame, not a whole stack
   stackFrame: {
     stringify: function format_stackFrame_stringify(obj) {
       return obj.name + " @ " + obj.filename + ":" + obj.lineNumber;
     }
   },
@@ -70,8 +90,58 @@ function stringifyTypedObj(obj, conditio
   if (conditionalStr == null)
     conditionalStr = "";
 
   if (!(obj.type in LogFormatters))
     return "";
 
   return conditionalStr + LogFormatters[obj.type].stringify(obj);
 }
+
+function nodifyTypedObj(obj) {
+  if (!(obj.type in LogFormatters))
+    return document.createTextNode("No formatter for type: " + obj.type);
+
+  let formatter = LogFormatters[obj.type];
+  // if there is no explicit nodifier, just stringify and link it.
+  if ("nodify" in formatter)
+    return formatter.nodify(obj);
+  else
+    return FormatHelp.makeDetailLink(formatter.stringify(obj), obj);
+}
+
+function nodifyThing(obj, genericObjHandler) {
+  if (obj == null) {
+    return document.createTextNode("null");
+  }
+  else if (typeof(obj) == "string") {
+    return document.createTextNode(obj);
+  }
+  else if (typeof(obj) != "object") {
+    return document.createTextNode(obj.toString());
+  }
+  else if ("type" in obj) {
+    return nodifyTypedObj(obj);
+  }
+  else if ("length" in obj) {
+    return nodifyList(obj, genericObjHandler, true);
+  }
+  else if (genericObjHandler) {
+    return genericObjHandler(obj);
+  }
+  else {
+    return document.createTextNode(obj.toString());
+  }
+};
+
+function nodifyList(things, genericObjHandler, delimit) {
+  let span = document.createElement("span");
+  for each (let [iThing, thing] in Iterator(things)) {
+    if (iThing) {
+      if (delimit)
+        span.appendChild(document.createTextNode(", "));
+      else
+        span.appendChild(document.createTextNode(" "));
+    }
+    span.appendChild(nodifyThing(thing, genericObjHandler));
+  }
+  return span;
+}
--- a/chrome/content/logsploder.xul
+++ b/chrome/content/logsploder.xul
@@ -1,11 +1,11 @@
 <?xml version="1.0"?>
 <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
 
-<window id="main" title="LogSploder" width="1024" height="768"
+<window id="main" title="LogSploder" width="1024" height="1200"
 xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
   <iframe id="viewerframe" flex="2"
     src="chrome://logsploder/content/logviewer.xhtml"/>
   <!-- 
   <iframe id="blah" flex="1" src="chrome://global/content/console.xul"/>
   -->
 </window>
--- a/chrome/content/logui.js
+++ b/chrome/content/logui.js
@@ -5,18 +5,27 @@
 /**
  * In charge of the various concepts of selection and such.
  */
 let LogUI = {
   _init: function LogUI__init() {
 
   },
 
+  /**
+   * Select a bucket and make it current.
+   *
+   * @param bucketAggr A bucket aggregation as provided by LogAggr.
+   */
   selectBucket: function LogUI_showBucket(bucketAggr) {
-    this._notifyListeners("onBucketSelected", [bucketAggr]);
+    this._notifyListeners("onBucketSelected", arguments);
+  },
+
+  showDetail: function LogUI_showDetail(obj, clickOrigin) {
+    this._notifyListeners("onShowDetail", arguments);
   },
 
   /**
    * Maps a listeningFor identifier to a list of listeners.
    */
   _listenersByListeningFor: {},
 
   _notifyListeners: function LogManager__notifyListeners(listeningFor, args) {
@@ -53,30 +62,62 @@ let LogList = {
 
     let bucketNode = document.getElementById("bucket-contents");
     while (bucketNode.lastChild)
       bucketNode.removeChild(bucketNode.lastChild);
 
     let listRoot = document.createElement("ul");
 
     for each (let [, msg] in Iterator(bucket)) {
-      let text = "";
+      // filter out contexts for now
+      let realThings = [];
       for each (let [, msgObj] in Iterator(msg.messageObjects)) {
-        // ignore contexts for this purpose
-        if (typeof(msgObj) != "object")
-          text += (text ? " " : "") + msgObj;
-        else if ("_isContext" in msgObj)
+        if (msgObj && (typeof(msgObj) == "object") &&
+            ("_isContext" in msgObj))
+          // in the future we would do something having seen this
           continue;
-        else if ("type" in msgObj)
-          text += stringifyTypedObj(msgObj, " ");
+        realThings.push(msgObj);
+      }
 
-        let listNode = document.createElement("li");
-        listNode.textContent = text;
-        listRoot.appendChild(listNode);
-      }
+      let listNode = document.createElement("li");
+      listNode.appendChild(nodifyList(realThings));
+      listRoot.appendChild(listNode);
     }
 
     bucketNode.appendChild(listRoot);
   },
-
-
 };
 LogList._init();
+
+/**
+ * Implements a primitive detail view that just builds a hierarchical definition
+ *  list.
+ */
+let DetailView = {
+  _init: function DetailView__init() {
+    LogUI.registerListener("onShowDetail", this.onShowDetail, this);
+  },
+
+  _buildDefTree: function DetailView__buildDefTree(obj) {
+    let listRoot = document.createElement("dl");
+    for each (let [key, val] in Iterator(obj)) {
+      let topic = document.createElement("dt");
+      topic.textContent = key;
+
+      let data = document.createElement("dd");
+      data.appendChild(nodifyThing(val), DetailView._buildDefTree);
+
+      listRoot.appendChild(topic);
+      listRoot.appendChild(data);
+    }
+    return listRoot;
+  },
+
+  onShowDetail: function DetailView_onShowDetail(obj) {
+    let detailNode = document.getElementById("detail-view");
+
+    while (detailNode.lastChild)
+      detailNode.removeChild(detailNode.lastChild);
+
+    detailNode.appendChild(this._buildDefTree(obj));
+  },
+};
+DetailView._init();
--- a/chrome/content/logviewer.css
+++ b/chrome/content/logviewer.css
@@ -0,0 +1,5 @@
+a {
+  color: #444488;
+  cursor: pointer;
+  font-weight: 500;
+}
--- a/chrome/content/logviewer.xhtml
+++ b/chrome/content/logviewer.xhtml
@@ -23,10 +23,11 @@
       type="text/css" />
   </head>
   <body bgcolor="#ffffff">
     <div id="visualizations" style="display: inline-block">
       <div id="logger-hierarchy-vis" style="float: left;"/>
       <div id="date-bucket-vis" style="float: left;"/>
     </div>
     <div id="bucket-contents"/>
+    <div id="detail-view"/>
   </body>
 </html>