Bug 1283123 - Reps: make it possible to pass in a component to handle object links. r=linclark,honza draft
authorNicolas Chevobbe <nch@atolcd.com>
Tue, 21 Jun 2016 22:56:10 +0200
changeset 382596 67352924a7ef6a933ae1fb565cfd0e31332f64e4
parent 382391 b69a5bbb5e40bd426e35222baa600b481e50d265
child 524243 475a356f0aa9e36914b18c80dc0efab5ab34d6c8
push id21775
push userbmo:lclark@mozilla.com
push dateWed, 29 Jun 2016 23:32:04 +0000
reviewerslinclark, honza
bugs1283123
milestone50.0a1
Bug 1283123 - Reps: make it possible to pass in a component to handle object links. r=linclark,honza MozReview-Commit-ID: 20xvwUua4WA
devtools/client/shared/components/reps/array.js
devtools/client/shared/components/reps/date-time.js
devtools/client/shared/components/reps/document.js
devtools/client/shared/components/reps/event.js
devtools/client/shared/components/reps/function.js
devtools/client/shared/components/reps/grip-array.js
devtools/client/shared/components/reps/grip.js
devtools/client/shared/components/reps/named-node-map.js
devtools/client/shared/components/reps/object-with-text.js
devtools/client/shared/components/reps/object-with-url.js
devtools/client/shared/components/reps/object.js
devtools/client/shared/components/reps/regexp.js
devtools/client/shared/components/reps/stylesheet.js
devtools/client/shared/components/reps/text-node.js
devtools/client/shared/components/reps/window.js
devtools/client/webconsole/new-console-output/components/variables-view-link.js
--- a/devtools/client/shared/components/reps/array.js
+++ b/devtools/client/shared/components/reps/array.js
@@ -57,19 +57,23 @@ define(function (require, exports, modul
             delim: delim,
             key: i
           }));
         }
       }
 
       if (array.length > max + 1) {
         items.pop();
+
+        let objectLink = this.props.objectLink || DOM.span;
         items.push(Caption({
           key: "more",
-          object: "more...",
+          object: objectLink({
+            objectActor: this.props.object
+          }, "more...")
         }));
       }
 
       return items;
     },
 
     /**
      * Returns true if the passed object is an array with additional (custom)
@@ -124,39 +128,32 @@ define(function (require, exports, modul
 
       if (mode == "tiny") {
         items = DOM.span({className: "length"}, object.length);
       } else {
         let max = (mode == "short") ? 3 : 300;
         items = this.arrayIterator(object, max);
       }
 
+      let objectLink = this.props.objectLink || DOM.span;
+
       return (
         ObjectBox({
-          className: "array",
-          onClick: this.onToggleProperties},
-          DOM.a({
-            className: "objectLink",
-            onclick: this.onClickBracket},
-            DOM.span({
-              className: "arrayLeftBracket",
-              role: "presentation"},
-              "["
-            )
-          ),
+          className: "array"},
+          objectLink({
+            className: "arrayLeftBracket",
+            role: "presentation",
+            objectActor: object
+          }, "["),
           items,
-          DOM.a({
-            className: "objectLink",
-            onclick: this.onClickBracket},
-            DOM.span({
-              className: "arrayRightBracket",
-              role: "presentation"},
-              "]"
-            )
-          ),
+          objectLink({
+            className: "arrayRightBracket",
+            role: "presentation",
+            objectActor: object
+          }, "]"),
           DOM.span({
             className: "arrayProperties",
             role: "group"}
           )
         )
       );
     },
   });
--- a/devtools/client/shared/components/reps/date-time.js
+++ b/devtools/client/shared/components/reps/date-time.js
@@ -7,41 +7,47 @@
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
 
   // Reps
   const { createFactories, isGrip } = require("./rep-utils");
-  const { ObjectLink } = createFactories(require("./object-link"));
+  const { ObjectBox } = createFactories(require("./object-box"));
 
   // Shortcuts
   const { span } = React.DOM;
 
   /**
    * Used to render JS built-in Date() object.
    */
   let DateTime = React.createClass({
     displayName: "Date",
 
     propTypes: {
       object: React.PropTypes.object.isRequired
     },
 
     getTitle: function (grip) {
-      return new Date(grip.preview.timestamp).toISOString();
+      if (this.props.objectLink) {
+        return this.props.objectLink({
+          objectActor: grip
+        }, grip.class);
+      }
+      return "";
     },
 
     render: function () {
       let grip = this.props.object;
       return (
-        ObjectLink({className: "Date"},
-          span({className: "objectTitle"},
-            this.getTitle(grip)
+        ObjectBox({},
+          this.getTitle(grip),
+          span({className: "Date"},
+            new Date(grip.preview.timestamp).toString()
           )
         )
       );
     },
   });
 
   // Registration
 
--- a/devtools/client/shared/components/reps/document.js
+++ b/devtools/client/shared/components/reps/document.js
@@ -28,29 +28,37 @@ define(function (require, exports, modul
       object: React.PropTypes.object.isRequired
     },
 
     getLocation: function (grip) {
       let location = grip.preview.location;
       return location ? getFileName(location) : "";
     },
 
-    getTitle: function (win, context) {
-      return "document";
+    getTitle: function (grip) {
+      if (this.props.objectLink) {
+        return ObjectBox({},
+          this.props.objectLink({
+            objectActor: grip
+          }, grip.class)
+        );
+      }
+      return "";
     },
 
     getTooltip: function (doc) {
       return doc.location.href;
     },
 
     render: function () {
       let grip = this.props.object;
 
       return (
         ObjectBox({className: "object"},
+          this.getTitle(grip),
           span({className: "objectPropValue"},
             this.getLocation(grip)
           )
         )
       );
     },
   });
 
--- a/devtools/client/shared/components/reps/event.js
+++ b/devtools/client/shared/components/reps/event.js
@@ -7,30 +7,39 @@
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
 
   // Reps
   const { createFactories, isGrip } = require("./rep-utils");
-  const { ObjectLink } = createFactories(require("./object-link"));
+  const { ObjectBox } = createFactories(require("./object-box"));
 
   /**
    * Renders DOM event objects.
    */
   let Event = React.createClass({
     displayName: "event",
 
     propTypes: {
       object: React.PropTypes.object.isRequired
     },
 
+    getTitle: function (grip) {
+      if (this.props.objectLink) {
+        return this.props.objectLink({
+          objectActor: grip
+        }, grip.preview.type);
+      }
+      return grip.preview.type;
+    },
+
     summarizeEvent: function (grip) {
-      let info = [grip.preview.type, " "];
+      let info = [];
 
       let eventFamily = grip.class;
       let props = grip.preview.properties;
 
       if (eventFamily == "MouseEvent") {
         info.push("clientX=", props.clientX, ", clientY=", props.clientY);
       } else if (eventFamily == "KeyboardEvent") {
         info.push("charCode=", props.charCode, ", keyCode=", props.keyCode);
@@ -39,17 +48,18 @@ define(function (require, exports, modul
       }
 
       return info.join("");
     },
 
     render: function () {
       let grip = this.props.object;
       return (
-        ObjectLink({className: "event"},
+        ObjectBox({className: "event"},
+          this.getTitle(grip),
           this.summarizeEvent(grip)
         )
       );
     },
   });
 
   // Registration
 
--- a/devtools/client/shared/components/reps/function.js
+++ b/devtools/client/shared/components/reps/function.js
@@ -7,39 +7,49 @@
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
 
   // Reps
   const { createFactories, isGrip } = require("./rep-utils");
-  const { ObjectLink } = createFactories(require("./object-link"));
+  const { ObjectBox } = createFactories(require("./object-box"));
   const { cropString } = require("./string");
 
   /**
    * This component represents a template for Function objects.
    */
   let Func = React.createClass({
     displayName: "Func",
 
     propTypes: {
       object: React.PropTypes.object.isRequired
     },
 
+    getTitle: function (grip) {
+      if (this.props.objectLink) {
+        return this.props.objectLink({
+          objectActor: grip
+        }, "function");
+      }
+      return "";
+    },
+
     summarizeFunction: function (grip) {
       let name = grip.displayName || grip.name || "function";
       return cropString(name + "()", 100);
     },
 
     render: function () {
       let grip = this.props.object;
 
       return (
-        ObjectLink({className: "function"},
+        ObjectBox({className: "function"},
+          this.getTitle(grip),
           this.summarizeFunction(grip)
         )
       );
     },
   });
 
   // Registration
 
--- a/devtools/client/shared/components/reps/grip-array.js
+++ b/devtools/client/shared/components/reps/grip-array.js
@@ -10,17 +10,17 @@
 define(function (require, exports, module) {
   // Dependencies
   const React = require("devtools/client/shared/vendor/react");
   const { createFactories, isGrip } = require("./rep-utils");
   const { ObjectBox } = createFactories(require("./object-box"));
   const { Caption } = createFactories(require("./caption"));
 
   // Shortcuts
-  const { a, span } = React.DOM;
+  const { span } = React.DOM;
 
   /**
    * Renders an array. The array is enclosed by left and right bracket
    * and the max number of rendered items depends on the current mode.
    */
   let GripArray = React.createClass({
     displayName: "GripArray",
 
@@ -30,17 +30,22 @@ define(function (require, exports, modul
       provider: React.PropTypes.object,
     },
 
     getLength: function (grip) {
       return grip.preview ? grip.preview.length : 0;
     },
 
     getTitle: function (object, context) {
-      return "[" + object.length + "]";
+      if (this.props.objectLink) {
+        return this.props.objectLink({
+          objectActor: object
+        }, object.class);
+      }
+      return "";
     },
 
     arrayIterator: function (grip, max) {
       let items = [];
 
       if (!grip.preview || !grip.preview.length) {
         return items;
       }
@@ -82,20 +87,23 @@ define(function (require, exports, modul
             delim: delim,
             key: i}
           )));
         }
       }
 
       if (array.length > max + 1) {
         items.pop();
+        let objectLink = this.props.objectLink || span;
         items.push(Caption({
           key: "more",
-          object: "more..."}
-        ));
+          object: objectLink({
+            objectActor: this.props.object
+          }, "more...")
+        }));
       }
 
       return items;
     },
 
     hasSpecialProperties: function (array) {
       return false;
     },
@@ -116,39 +124,33 @@ define(function (require, exports, modul
 
       if (mode == "tiny") {
         items = span({className: "length"}, this.getLength(object));
       } else {
         let max = (mode == "short") ? 3 : 300;
         items = this.arrayIterator(object, max);
       }
 
+      let objectLink = this.props.objectLink || span;
+
       return (
         ObjectBox({
-          className: "array",
-          onClick: this.onToggleProperties},
-          a({
-            className: "objectLink",
-            onclick: this.onClickBracket},
-            span({
-              className: "arrayLeftBracket",
-              role: "presentation"},
-              "["
-            )
-          ),
+          className: "array"},
+          this.getTitle(object),
+          objectLink({
+            className: "arrayLeftBracket",
+            role: "presentation",
+            objectActor: object
+          }, "["),
           items,
-          a({
-            className: "objectLink",
-            onclick: this.onClickBracket},
-            span({
-              className: "arrayRightBracket",
-              role: "presentation"},
-              "]"
-            )
-          ),
+          objectLink({
+            className: "arrayRightBracket",
+            role: "presentation",
+            objectActor: object
+          }, "]"),
           span({
             className: "arrayProperties",
             role: "group"}
           )
         )
       );
     },
   });
--- a/devtools/client/shared/components/reps/grip.js
+++ b/devtools/client/shared/components/reps/grip.js
@@ -25,18 +25,23 @@ define(function (require, exports, modul
   const Grip = React.createClass({
     displayName: "Grip",
 
     propTypes: {
       object: React.PropTypes.object.isRequired,
       mode: React.PropTypes.string,
     },
 
-    getTitle: function () {
-      return this.props.object.class || "Object";
+    getTitle: function (object) {
+      if (this.props.objectLink) {
+        return this.props.objectLink({
+          objectActor: object
+        }, object.class);
+      }
+      return "";
     },
 
     longPropIterator: function (object) {
       try {
         return this.propIterator(object, 100);
       } catch (err) {
         console.error(err);
       }
@@ -76,19 +81,24 @@ define(function (require, exports, modul
         }));
       }
 
       // getProps() can return max+1 properties (it can't return more)
       // to indicate that there is more props than allowed. Remove the last
       // one and append 'more...' postfix in such case.
       if (props.length > max) {
         props.pop();
+
+        let objectLink = this.props.objectLink || span;
+
         props.push(Caption({
           key: "more",
-          object: "more...",
+          object: objectLink({
+            objectActor: object
+          }, "more...")
         }));
       } else if (props.length > 0) {
         // Remove the last comma.
         // NOTE: do not change comp._store.props directly to update a property,
         // it should be re-rendered or cloned with changed props
         let last = props.length - 1;
         props[last] = React.cloneElement(props[last], {
           delim: ""
@@ -141,30 +151,44 @@ define(function (require, exports, modul
     },
 
     render: function () {
       let object = this.props.object;
       let props = (this.props.mode == "long") ?
         this.longPropIterator(object) :
         this.shortPropIterator(object);
 
+      let objectLink = this.props.objectLink || span;
       if (this.props.mode == "tiny" || !props.length) {
         return (
           ObjectBox({className: "object"},
-            span({className: "objectTitle"}, this.getTitle(object))
+            this.getTitle(object),
+            objectLink({
+              className: "objectLeftBrace",
+              role: "presentation",
+              objectActor: object
+            }, "{}")
           )
         );
       }
 
       return (
         ObjectBox({className: "object"},
-          span({className: "objectTitle"}, this.getTitle(object)),
-          span({className: "objectLeftBrace", role: "presentation"}, " {"),
+          this.getTitle(object),
+          objectLink({
+            className: "objectLeftBrace",
+            role: "presentation",
+            objectActor: object
+          }, "{"),
           props,
-          span({className: "objectRightBrace"}, "}")
+          objectLink({
+            className: "objectRightBrace",
+            role: "presentation",
+            objectActor: object
+          }, "}")
         )
       );
     },
   });
 
   // Registration
   function supportsObject(object, type) {
     if (!isGrip(object)) {
--- a/devtools/client/shared/components/reps/named-node-map.js
+++ b/devtools/client/shared/components/reps/named-node-map.js
@@ -7,53 +7,60 @@
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
 
   // Reps
   const { createFactories, isGrip } = require("./rep-utils");
-  const { ObjectLink } = createFactories(require("./object-link"));
+  const { ObjectBox } = createFactories(require("./object-box"));
   const { Caption } = createFactories(require("./caption"));
 
   // Shortcuts
   const { span } = React.DOM;
 
   /**
    * Used to render a map of values provided as a grip.
    */
   let NamedNodeMap = React.createClass({
+    displayName: "NamedNodeMap",
 
     propTypes: {
       object: React.PropTypes.object.isRequired,
       mode: React.PropTypes.string,
       provider: React.PropTypes.object,
     },
 
-    className: "NamedNodeMap",
-
     getLength: function (object) {
       return object.preview.length;
     },
 
     getTitle: function (object) {
+      if (this.props.objectLink && object.class) {
+        return this.props.objectLink({
+          objectActor: object
+        }, object.class);
+      }
       return object.class ? object.class : "";
     },
 
     getItems: function (array, max) {
       let items = this.propIterator(array, max);
 
       items = items.map(item => PropRep(item));
 
       if (items.length > max + 1) {
         items.pop();
+        let objectLink = this.props.objectLink || span;
         items.push(Caption({
           key: "more",
-          object: "more...",
+          object: objectLink({
+            objectActor: this.props.object
+          }, "more...")
         }));
       }
 
       return items;
     },
 
     propIterator: function (grip, max) {
       max = max || 3;
@@ -93,32 +100,32 @@ define(function (require, exports, modul
       let items;
       if (mode == "tiny") {
         items = this.getLength(grip);
       } else {
         let max = (mode == "short") ? 3 : 100;
         items = this.getItems(grip, max);
       }
 
+      let objectLink = this.props.objectLink || span;
+
       return (
-        ObjectLink({className: "NamedNodeMap"},
-          span({className: "objectTitle"},
-            this.getTitle(grip)
-          ),
-          span({
+        ObjectBox({className: "NamedNodeMap"},
+          this.getTitle(grip),
+          objectLink({
             className: "arrayLeftBracket",
-            role: "presentation"},
-            "["
-          ),
+            role: "presentation",
+            objectActor: grip
+          }, "["),
           items,
-          span({
+          objectLink({
             className: "arrayRightBracket",
-            role: "presentation"},
-            "]"
-          )
+            role: "presentation",
+            objectActor: grip
+          }, "]")
         )
       );
     },
   });
 
   /**
    * Property for a grip object.
    */
--- a/devtools/client/shared/components/reps/object-with-text.js
+++ b/devtools/client/shared/components/reps/object-with-text.js
@@ -7,43 +7,55 @@
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
 
   // Reps
   const { createFactories, isGrip } = require("./rep-utils");
-  const { ObjectLink } = createFactories(require("./object-link"));
+  const { ObjectBox } = createFactories(require("./object-box"));
 
   // Shortcuts
   const { span } = React.DOM;
 
   /**
    * Renders a grip object with textual data.
    */
   let ObjectWithText = React.createClass({
     displayName: "ObjectWithText",
 
     propTypes: {
       object: React.PropTypes.object.isRequired,
     },
 
+    getTitle: function (grip) {
+      if (this.props.objectLink) {
+        return ObjectBox({},
+          this.props.objectLink({
+            objectActor: grip
+          }, this.getType(grip))
+        );
+      }
+      return "";
+    },
+
     getType: function (grip) {
       return grip.class;
     },
 
     getDescription: function (grip) {
       return (grip.preview.kind == "ObjectWithText") ? grip.preview.text : "";
     },
 
     render: function () {
       let grip = this.props.object;
       return (
-        ObjectLink({className: this.getType(grip)},
+        ObjectBox({className: this.getType(grip)},
+          this.getTitle(grip),
           span({className: "objectPropValue"},
             this.getDescription(grip)
           )
         )
       );
     },
   });
 
--- a/devtools/client/shared/components/reps/object-with-url.js
+++ b/devtools/client/shared/components/reps/object-with-url.js
@@ -7,43 +7,55 @@
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
 
   // Reps
   const { createFactories, isGrip } = require("./rep-utils");
-  const { ObjectLink } = createFactories(require("./object-link"));
+  const { ObjectBox } = createFactories(require("./object-box"));
 
   // Shortcuts
   const { span } = React.DOM;
 
   /**
    * Renders a grip object with URL data.
    */
   let ObjectWithURL = React.createClass({
     displayName: "ObjectWithURL",
 
     propTypes: {
       object: React.PropTypes.object.isRequired,
     },
 
+    getTitle: function (grip) {
+      if (this.props.objectLink) {
+        return ObjectBox({},
+          this.props.objectLink({
+            objectActor: grip
+          }, this.getType(grip))
+        );
+      }
+      return "";
+    },
+
     getType: function (grip) {
       return grip.class;
     },
 
     getDescription: function (grip) {
       return grip.preview.url;
     },
 
     render: function () {
       let grip = this.props.object;
       return (
-        ObjectLink({className: this.getType(grip)},
+        ObjectBox({className: this.getType(grip)},
+          this.getTitle(grip),
           span({className: "objectPropValue"},
             this.getDescription(grip)
           )
         )
       );
     },
   });
 
--- a/devtools/client/shared/components/reps/object.js
+++ b/devtools/client/shared/components/reps/object.js
@@ -22,18 +22,23 @@ define(function (require, exports, modul
   const Obj = React.createClass({
     displayName: "Obj",
 
     propTypes: {
       object: React.PropTypes.object,
       mode: React.PropTypes.string,
     },
 
-    getTitle: function () {
-      return "Object";
+    getTitle: function (object) {
+      if (this.props.objectLink) {
+        return this.props.objectLink({
+          objectActor: object
+        }, object.class);
+      }
+      return "";
     },
 
     longPropIterator: function (object) {
       try {
         return this.propIterator(object, 100);
       } catch (err) {
         console.error(err);
       }
@@ -70,19 +75,23 @@ define(function (require, exports, modul
         // Let's display also empty members and functions.
         props = props.concat(this.getProps(object, max, (t, value) => {
           return !isInterestingProp(t, value);
         }));
       }
 
       if (props.length > max) {
         props.pop();
+        let objectLink = this.props.objectLink || span;
+
         props.push(Caption({
           key: "more",
-          object: "more...",
+          object: objectLink({
+            objectActor: object
+          }, "more...")
         }));
       } else if (props.length > 0) {
         // Remove the last comma.
         props[props.length - 1] = React.cloneElement(
           props[props.length - 1], { delim: "" });
       }
 
       return props;
@@ -128,36 +137,38 @@ define(function (require, exports, modul
       }
 
       return props;
     },
 
     render: function () {
       let object = this.props.object;
       let props = this.shortPropIterator(object);
-
-      if (this.props.mode == "tiny" || !props.length) {
-        return (
-          ObjectBox({className: "object"},
-            span({className: "objectTitle"}, this.getTitle())
-          )
-        );
-      }
+      let objectLink = this.props.objectLink || span;
 
       return (
         ObjectBox({className: "object"},
-          span({className: "objectTitle"}, this.getTitle()),
-          span({className: "objectLeftBrace", role: "presentation"}, "{"),
+          this.getTitle(object),
+          objectLink({
+            className: "objectLeftBrace",
+            role: "presentation",
+            objectActor: object
+          }, "{"),
           props,
-          span({className: "objectRightBrace"}, "}")
+          objectLink({
+            className: "objectRightBrace",
+            role: "presentation",
+            objectActor: object
+          }, "}")
         )
       );
     },
   });
   function supportsObject(object, type) {
     return true;
   }
+
   // Exports from this module
   exports.Obj = {
     rep: Obj,
     supportsObject: supportsObject
   };
 });
--- a/devtools/client/shared/components/reps/regexp.js
+++ b/devtools/client/shared/components/reps/regexp.js
@@ -7,50 +7,45 @@
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
 
   // Reps
   const { createFactories, isGrip } = require("./rep-utils");
-  const { ObjectLink } = createFactories(require("./object-link"));
+  const { ObjectBox } = createFactories(require("./object-box"));
 
   // Shortcuts
   const { span } = React.DOM;
 
   /**
    * Renders a grip object with regular expression.
    */
   let RegExp = React.createClass({
     displayName: "regexp",
 
     propTypes: {
       object: React.PropTypes.object.isRequired,
     },
 
-    getTitle: function (grip) {
-      return grip.class;
-    },
-
     getSource: function (grip) {
       return grip.displayString;
     },
 
     render: function () {
       let grip = this.props.object;
+      let objectLink = this.props.objectLink || span;
+
       return (
-        ObjectLink({className: "regexp"},
-          span({className: "objectTitle"},
-            this.getTitle(grip)
-          ),
-          span(" "),
-          span({className: "regexpSource"},
-            this.getSource(grip)
-          )
+        ObjectBox({className: "regexp"},
+          objectLink({
+            objectActor: grip,
+            className: "regexpSource"
+          }, this.getSource(grip))
         )
       );
     },
   });
 
   // Registration
 
   function supportsObject(object, type) {
--- a/devtools/client/shared/components/reps/stylesheet.js
+++ b/devtools/client/shared/components/reps/stylesheet.js
@@ -23,28 +23,40 @@ define(function (require, exports, modul
    */
   let StyleSheet = React.createClass({
     displayName: "object",
 
     propTypes: {
       object: React.PropTypes.object.isRequired,
     },
 
+    getTitle: function (grip) {
+      let title = "StyleSheet";
+      if (this.props.objectLink) {
+        return ObjectBox({},
+          this.props.objectLink({
+            objectActor: grip
+          }, title)
+        );
+      }
+      return title;
+    },
+
     getLocation: function (grip) {
       // Embedded stylesheets don't have URL and so, no preview.
       let url = grip.preview ? grip.preview.url : "";
       return url ? getFileName(url) : "";
     },
 
     render: function () {
       let grip = this.props.object;
 
       return (
         ObjectBox({className: "object"},
-          "StyleSheet ",
+          this.getTitle(grip),
           DOM.span({className: "objectPropValue"},
             this.getLocation(grip)
           )
         )
       );
     },
   });
 
--- a/devtools/client/shared/components/reps/text-node.js
+++ b/devtools/client/shared/components/reps/text-node.js
@@ -7,17 +7,17 @@
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
 
   // Reps
   const { createFactories, isGrip } = require("./rep-utils");
-  const { ObjectLink } = createFactories(require("./object-link"));
+  const { ObjectBox } = createFactories(require("./object-box"));
   const { cropMultipleLines } = require("./string");
 
   // Shortcuts
   const DOM = React.DOM;
 
   /**
    * Renders DOM #text node.
    */
@@ -28,42 +28,54 @@ define(function (require, exports, modul
       object: React.PropTypes.object.isRequired,
       mode: React.PropTypes.string,
     },
 
     getTextContent: function (grip) {
       return cropMultipleLines(grip.preview.textContent);
     },
 
-    getTitle: function (win, context) {
-      return "textNode";
+    getTitle: function (grip) {
+      if (this.props.objectLink) {
+        return this.props.objectLink({
+          objectActor: grip
+        }, "#text");
+      }
+      return "";
     },
 
     render: function () {
       let grip = this.props.object;
       let mode = this.props.mode || "short";
 
       if (mode == "short" || mode == "tiny") {
         return (
-          ObjectLink({className: "textNode"},
+          ObjectBox({className: "textNode"},
+            this.getTitle(grip),
             "\"" + this.getTextContent(grip) + "\""
           )
         );
       }
 
+      let objectLink = this.props.objectLink || DOM.span;
       return (
-        ObjectLink({className: "textNode"},
-          "<",
+        ObjectBox({className: "textNode"},
+          this.getTitle(grip),
+          objectLink({
+            objectActor: grip
+          }, "<"),
           DOM.span({className: "nodeTag"}, "TextNode"),
           " textContent=\"",
           DOM.span({className: "nodeValue"},
             this.getTextContent(grip)
           ),
           "\"",
-          ">;"
+          objectLink({
+            objectActor: grip
+          }, ">;")
         )
       );
     },
   });
 
   // Registration
 
   function supportsObject(grip, type) {
--- a/devtools/client/shared/components/reps/window.js
+++ b/devtools/client/shared/components/reps/window.js
@@ -23,25 +23,37 @@ define(function (require, exports, modul
    */
   let Window = React.createClass({
     displayName: "Window",
 
     propTypes: {
       object: React.PropTypes.object.isRequired,
     },
 
+    getTitle: function (grip) {
+      if (this.props.objectLink) {
+        return ObjectBox({},
+          this.props.objectLink({
+            objectActor: grip
+          }, grip.class)
+        );
+      }
+      return "";
+    },
+
     getLocation: function (grip) {
       return cropString(grip.preview.url);
     },
 
     render: function () {
       let grip = this.props.object;
 
       return (
         ObjectBox({className: "Window"},
+          this.getTitle(grip),
           DOM.span({className: "objectPropValue"},
             this.getLocation(grip)
           )
         )
       );
     },
   });
 
--- a/devtools/client/webconsole/new-console-output/components/variables-view-link.js
+++ b/devtools/client/webconsole/new-console-output/components/variables-view-link.js
@@ -11,24 +11,25 @@ const {
   DOM: dom,
   PropTypes
 } = require("devtools/client/shared/vendor/react");
 const {openVariablesView} = require("devtools/client/webconsole/new-console-output/utils/variables-view");
 
 VariablesViewLink.displayName = "VariablesViewLink";
 
 VariablesViewLink.propTypes = {
-  objectActor: PropTypes.object.required,
-  label: PropTypes.string.label,
+  objectActor: PropTypes.object.required
 };
 
 function VariablesViewLink(props) {
-  const { objectActor, label } = props;
+  const { objectActor, children } = props;
 
-  return dom.a({
-    onClick: openVariablesView.bind(null, objectActor),
-    className: "cm-variable",
-    draggable: false,
-    href: "#"
-  }, label);
+  return (
+    dom.a({
+      onClick: openVariablesView.bind(null, objectActor),
+      className: "cm-variable",
+      draggable: false,
+      href: "#"
+    }, children)
+  );
 }
 
 module.exports.VariablesViewLink = VariablesViewLink;