Bug 1351565 - update reps to v0.5.0;r=nchevobbe draft
authorJulian Descottes <jdescottes@mozilla.com>
Wed, 29 Mar 2017 10:05:14 +0200
changeset 552935 45c6cef6b04c6e1d8a299e3c624e0301612d43cd
parent 552934 1fe08782e2b94b721845410c6ee6d2588526ecc8
child 552991 412d15167708f9edbafc63aaa94ee6864a3087d5
push id51513
push userjdescottes@mozilla.com
push dateWed, 29 Mar 2017 08:26:49 +0000
reviewersnchevobbe
bugs1351565
milestone55.0a1
Bug 1351565 - update reps to v0.5.0;r=nchevobbe MozReview-Commit-ID: 2Q7isM1jF2P
devtools/client/shared/components/reps/reps.js
devtools/client/shared/components/reps/test/mochitest/test_reps_array.html
devtools/client/shared/components/reps/test/mochitest/test_reps_attribute.html
devtools/client/shared/components/reps/test/mochitest/test_reps_comment-node.html
devtools/client/shared/components/reps/test/mochitest/test_reps_date-time.html
devtools/client/shared/components/reps/test/mochitest/test_reps_document.html
devtools/client/shared/components/reps/test/mochitest/test_reps_element-node.html
devtools/client/shared/components/reps/test/mochitest/test_reps_error.html
devtools/client/shared/components/reps/test/mochitest/test_reps_event.html
devtools/client/shared/components/reps/test/mochitest/test_reps_failure.html
devtools/client/shared/components/reps/test/mochitest/test_reps_function.html
devtools/client/shared/components/reps/test/mochitest/test_reps_grip-array.html
devtools/client/shared/components/reps/test/mochitest/test_reps_grip-map.html
devtools/client/shared/components/reps/test/mochitest/test_reps_grip.html
devtools/client/shared/components/reps/test/mochitest/test_reps_infinity.html
devtools/client/shared/components/reps/test/mochitest/test_reps_long-string.html
devtools/client/shared/components/reps/test/mochitest/test_reps_nan.html
devtools/client/shared/components/reps/test/mochitest/test_reps_null.html
devtools/client/shared/components/reps/test/mochitest/test_reps_number.html
devtools/client/shared/components/reps/test/mochitest/test_reps_object-with-text.html
devtools/client/shared/components/reps/test/mochitest/test_reps_object-with-url.html
devtools/client/shared/components/reps/test/mochitest/test_reps_object.html
devtools/client/shared/components/reps/test/mochitest/test_reps_promise.html
devtools/client/shared/components/reps/test/mochitest/test_reps_regexp.html
devtools/client/shared/components/reps/test/mochitest/test_reps_string.html
devtools/client/shared/components/reps/test/mochitest/test_reps_stylesheet.html
devtools/client/shared/components/reps/test/mochitest/test_reps_symbol.html
devtools/client/shared/components/reps/test/mochitest/test_reps_text-node.html
devtools/client/shared/components/reps/test/mochitest/test_reps_undefined.html
devtools/client/shared/components/reps/test/mochitest/test_reps_window.html
--- a/devtools/client/shared/components/reps/reps.js
+++ b/devtools/client/shared/components/reps/reps.js
@@ -878,17 +878,17 @@ return /******/ (function(modules) { // 
 	
 	/**
 	 * Renders a number
 	 */
 	const Number = React.createClass({
 	  displayName: "Number",
 	
 	  propTypes: {
-	    object: React.PropTypes.oneOfType([React.PropTypes.object, React.PropTypes.number]).isRequired
+	    object: React.PropTypes.oneOfType([React.PropTypes.object, React.PropTypes.number, React.PropTypes.bool]).isRequired
 	  },
 	
 	  stringify: function (object) {
 	    let isNegativeZero = Object.is(object, -0) || object.type && object.type == "-0";
 	
 	    return isNegativeZero ? "-0" : String(object);
 	  },
 	
@@ -968,19 +968,18 @@ return /******/ (function(modules) { // 
 	          object: exc,
 	          mode: MODE.TINY,
 	          delim: delim
 	        }));
 	      }
 	    }
 	
 	    if (array.length > max) {
-	      let objectLink = this.props.objectLink || DOM.span;
 	      items.push(Caption({
-	        object: objectLink({
+	        object: this.safeObjectLink({
 	          object: this.props.object
 	        }, array.length - max + " more…")
 	      }));
 	    }
 	
 	    return items;
 	  },
 	
@@ -1023,16 +1022,30 @@ return /******/ (function(modules) { // 
 	  },
 	
 	  // Event Handlers
 	
 	  onToggleProperties: function (event) {},
 	
 	  onClickBracket: function (event) {},
 	
+	  safeObjectLink: function (config, ...children) {
+	    if (this.props.objectLink) {
+	      return this.props.objectLink(Object.assign({
+	        object: this.props.object
+	      }, config), ...children);
+	    }
+	
+	    if (Object.keys(config).length === 0 && children.length === 1) {
+	      return children[0];
+	    }
+	
+	    return DOM.span(config, ...children);
+	  },
+	
 	  render: wrapRender(function () {
 	    let {
 	      object,
 	      mode = MODE.SHORT
 	    } = this.props;
 	
 	    let items;
 	    let brackets;
@@ -1045,23 +1058,21 @@ return /******/ (function(modules) { // 
 	      items = [DOM.span({ className: "length" }, isEmpty ? "" : object.length)];
 	      brackets = needSpace(false);
 	    } else {
 	      let max = mode === MODE.SHORT ? 3 : 10;
 	      items = this.arrayIterator(object, max);
 	      brackets = needSpace(items.length > 0);
 	    }
 	
-	    let objectLink = this.props.objectLink || DOM.span;
-	
 	    return DOM.span({
-	      className: "objectBox objectBox-array" }, objectLink({
+	      className: "objectBox objectBox-array" }, this.safeObjectLink({
 	      className: "arrayLeftBracket",
 	      object: object
-	    }, brackets.left), ...items, objectLink({
+	    }, brackets.left), ...items, this.safeObjectLink({
 	      className: "arrayRightBracket",
 	      object: object
 	    }, brackets.right), DOM.span({
 	      className: "arrayProperties",
 	      role: "group" }));
 	  })
 	});
 	
@@ -1110,17 +1121,17 @@ return /******/ (function(modules) { // 
 	/**
 	 * Renders a caption. This template is used by other components
 	 * that needs to distinguish between a simple text/value and a label.
 	 */
 	const Caption = React.createClass({
 	  displayName: "Caption",
 	
 	  propTypes: {
-	    object: React.PropTypes.object
+	    object: React.PropTypes.oneOfType([React.PropTypes.number, React.PropTypes.string]).isRequired
 	  },
 	
 	  render: wrapRender(function () {
 	    return DOM.span({ "className": "caption" }, this.props.object);
 	  })
 	});
 	
 	// Exports from this module
@@ -1152,22 +1163,17 @@ return /******/ (function(modules) { // 
 	    // @TODO Change this to Object.values once it's supported in Node's version of V8
 	    mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
 	    objectLink: React.PropTypes.func,
 	    title: React.PropTypes.string
 	  },
 	
 	  getTitle: function (object) {
 	    let title = this.props.title || object.class || "Object";
-	    if (this.props.objectLink) {
-	      return this.props.objectLink({
-	        object: object
-	      }, title);
-	    }
-	    return title;
+	    return this.safeObjectLink({ className: "objectTitle" }, title);
 	  },
 	
 	  safePropIterator: function (object, max) {
 	    max = typeof max === "undefined" ? 3 : max;
 	    try {
 	      return this.propIterator(object, max);
 	    } catch (err) {
 	      console.error(err);
@@ -1253,31 +1259,42 @@ return /******/ (function(modules) { // 
 	      }
 	    } catch (err) {
 	      console.error(err);
 	    }
 	
 	    return propsArray;
 	  },
 	
+	  safeObjectLink: function (config, ...children) {
+	    if (this.props.objectLink) {
+	      return this.props.objectLink(Object.assign({
+	        object: this.props.object
+	      }, config), ...children);
+	    }
+	
+	    if (Object.keys(config).length === 0 && children.length === 1) {
+	      return children[0];
+	    }
+	
+	    return span(config, ...children);
+	  },
+	
 	  render: wrapRender(function () {
 	    let object = this.props.object;
 	    let propsArray = this.safePropIterator(object);
-	    let objectLink = this.props.objectLink || span;
 	
 	    if (this.props.mode === MODE.TINY || !propsArray.length) {
-	      return span({ className: "objectBox objectBox-object" }, objectLink({ className: "objectTitle" }, this.getTitle(object)));
+	      return span({ className: "objectBox objectBox-object" }, this.getTitle(object));
 	    }
 	
-	    return span({ className: "objectBox objectBox-object" }, this.getTitle(object), objectLink({
-	      className: "objectLeftBrace",
-	      object: object
-	    }, " { "), ...propsArray, objectLink({
-	      className: "objectRightBrace",
-	      object: object
+	    return span({ className: "objectBox objectBox-object" }, this.getTitle(object), this.safeObjectLink({
+	      className: "objectLeftBrace"
+	    }, " { "), ...propsArray, this.safeObjectLink({
+	      className: "objectRightBrace"
 	    }, " }"));
 	  })
 	});
 	function supportsObject(object, type) {
 	  return true;
 	}
 	
 	// Exports from this module
@@ -1403,22 +1420,17 @@ return /******/ (function(modules) { // 
 	    attachedActorIds: React.PropTypes.array,
 	    onDOMNodeMouseOver: React.PropTypes.func,
 	    onDOMNodeMouseOut: React.PropTypes.func,
 	    onInspectIconClick: React.PropTypes.func
 	  },
 	
 	  getTitle: function (object) {
 	    let title = this.props.title || object.class || "Object";
-	    if (this.props.objectLink) {
-	      return this.props.objectLink({
-	        object: object
-	      }, title);
-	    }
-	    return title;
+	    return this.safeObjectLink({}, title);
 	  },
 	
 	  safePropIterator: function (object, max) {
 	    max = typeof max === "undefined" ? 3 : max;
 	    try {
 	      return this.propIterator(object, max);
 	    } catch (err) {
 	      console.error(err);
@@ -1462,22 +1474,18 @@ return /******/ (function(modules) { // 
 	    // The server synthesizes some property names for a Proxy, like
 	    // <target> and <handler>; we don't want to quote these because,
 	    // as synthetic properties, they appear more natural when
 	    // unquoted.
 	    const suppressQuotes = object.class === "Proxy";
 	    let propsArray = this.getProps(properties, indexes, truncate, suppressQuotes);
 	    if (truncate) {
 	      // There are some undisplayed props. Then display "more...".
-	      let objectLink = this.props.objectLink || span;
-	
 	      propsArray.push(Caption({
-	        object: objectLink({
-	          object: object
-	        }, `${propertiesLength - max} more…`)
+	        object: this.safeObjectLink({}, `${propertiesLength - max} more…`)
 	      }));
 	    }
 	
 	    return propsArray;
 	  },
 	
 	  /**
 	   * Get props ordered by index.
@@ -1496,27 +1504,29 @@ return /******/ (function(modules) { // 
 	    indexes.sort(function (a, b) {
 	      return a - b;
 	    });
 	
 	    indexes.forEach(i => {
 	      let name = Object.keys(properties)[i];
 	      let value = this.getPropValue(properties[name]);
 	
-	      propsArray.push(PropRep(Object.assign({}, this.props, {
+	      let propRepProps = Object.assign({}, this.props, {
 	        mode: MODE.TINY,
 	        name: name,
 	        object: value,
 	        equal: ": ",
 	        delim: i !== indexes.length - 1 || truncate ? ", " : "",
 	        defaultRep: Grip,
 	        // Do not propagate title to properties reps
 	        title: undefined,
 	        suppressQuotes
-	      })));
+	      });
+	      delete propRepProps.objectLink;
+	      propsArray.push(PropRep(propRepProps));
 	    });
 	
 	    return propsArray;
 	  },
 	
 	  /**
 	   * Get the indexes of props in the object.
 	   *
@@ -1566,34 +1576,42 @@ return /******/ (function(modules) { // 
 	        value = property.value;
 	      } else if (keys.includes("getterValue")) {
 	        value = property.getterValue;
 	      }
 	    }
 	    return value;
 	  },
 	
+	  safeObjectLink: function (config, ...children) {
+	    if (this.props.objectLink) {
+	      return this.props.objectLink(Object.assign({
+	        object: this.props.object
+	      }, config), ...children);
+	    }
+	
+	    if (Object.keys(config).length === 0 && children.length === 1) {
+	      return children[0];
+	    }
+	
+	    return span(config, ...children);
+	  },
+	
 	  render: wrapRender(function () {
 	    let object = this.props.object;
 	    let propsArray = this.safePropIterator(object, this.props.mode === MODE.LONG ? 10 : 3);
 	
-	    let objectLink = this.props.objectLink || span;
 	    if (this.props.mode === MODE.TINY) {
-	      return span({ className: "objectBox objectBox-object" }, this.getTitle(object), objectLink({
-	        className: "objectLeftBrace",
-	        object: object
-	      }, ""));
+	      return span({ className: "objectBox objectBox-object" }, this.getTitle(object));
 	    }
 	
-	    return span({ className: "objectBox objectBox-object" }, this.getTitle(object), objectLink({
-	      className: "objectLeftBrace",
-	      object: object
-	    }, " { "), ...propsArray, objectLink({
-	      className: "objectRightBrace",
-	      object: object
+	    return span({ className: "objectBox objectBox-object" }, this.getTitle(object), this.safeObjectLink({
+	      className: "objectLeftBrace"
+	    }, " { "), ...propsArray, this.safeObjectLink({
+	      className: "objectRightBrace"
 	    }, " }"));
 	  })
 	});
 	
 	// Registration
 	function supportsObject(object, type) {
 	  if (!isGrip(object)) {
 	    return false;
@@ -1752,19 +1770,24 @@ return /******/ (function(modules) { // 
 	
 	  getTitle: function (grip) {
 	    return grip.preview.nodeName;
 	  },
 	
 	  render: wrapRender(function () {
 	    let object = this.props.object;
 	    let value = object.preview.value;
-	    let objectLink = this.props.objectLink || span;
-	
-	    return objectLink({ className: "objectLink-Attr", object }, span({}, span({ className: "attrTitle" }, this.getTitle(object)), span({ className: "attrEqual" }, "="), StringRepFactory({ object: value })));
+	    let objectLink = (config, ...children) => {
+	      if (this.props.objectLink) {
+	        return this.props.objectLink(Object.assign({ object }, config), ...children);
+	      }
+	      return span(config, ...children);
+	    };
+	
+	    return objectLink({ className: "objectLink-Attr" }, span({ className: "attrTitle" }, this.getTitle(object)), span({ className: "attrEqual" }, "="), StringRepFactory({ object: value }));
 	  })
 	});
 	
 	// Registration
 	
 	function supportsObject(grip, type) {
 	  if (!isGrip(grip)) {
 	    return false;
@@ -2131,22 +2154,17 @@ return /******/ (function(modules) { // 
 	    attachedActorIds: React.PropTypes.array,
 	    onDOMNodeMouseOver: React.PropTypes.func,
 	    onDOMNodeMouseOut: React.PropTypes.func,
 	    onInspectIconClick: React.PropTypes.func
 	  },
 	
 	  getTitle: function (object) {
 	    const title = object.class;
-	    if (this.props.objectLink) {
-	      return this.props.objectLink({
-	        object: object
-	      }, title);
-	    }
-	    return title;
+	    return this.safeObjectLink({}, title);
 	  },
 	
 	  getProps: function (promiseState) {
 	    const keys = ["state"];
 	    if (Object.keys(promiseState).includes("value")) {
 	      keys.push("value");
 	    }
 	
@@ -2158,40 +2176,49 @@ return /******/ (function(modules) { // 
 	        object,
 	        equal: ": ",
 	        delim: i < keys.length - 1 ? ", " : "",
 	        suppressQuotes: true
 	      }));
 	    });
 	  },
 	
+	  safeObjectLink: function (config, ...children) {
+	    if (this.props.objectLink) {
+	      return this.props.objectLink(Object.assign({
+	        object: this.props.object
+	      }, config), ...children);
+	    }
+	
+	    if (Object.keys(config).length === 0 && children.length === 1) {
+	      return children[0];
+	    }
+	
+	    return span(config, ...children);
+	  },
+	
 	  render: wrapRender(function () {
 	    const object = this.props.object;
 	    const { promiseState } = object;
-	    let objectLink = this.props.objectLink || span;
 	
 	    if (this.props.mode === MODE.TINY) {
 	      let { Rep } = createFactories(__webpack_require__(2));
 	
-	      return span({ className: "objectBox objectBox-object" }, this.getTitle(object), objectLink({
-	        className: "objectLeftBrace",
-	        object: object
-	      }, " { "), Rep({ object: promiseState.state }), objectLink({
-	        className: "objectRightBrace",
-	        object: object
+	      return span({ className: "objectBox objectBox-object" }, this.getTitle(object), this.safeObjectLink({
+	        className: "objectLeftBrace"
+	      }, " { "), Rep({ object: promiseState.state }), this.safeObjectLink({
+	        className: "objectRightBrace"
 	      }, " }"));
 	    }
 	
 	    const propsArray = this.getProps(promiseState);
-	    return span({ className: "objectBox objectBox-object" }, this.getTitle(object), objectLink({
-	      className: "objectLeftBrace",
-	      object: object
-	    }, " { "), ...propsArray, objectLink({
-	      className: "objectRightBrace",
-	      object: object
+	    return span({ className: "objectBox objectBox-object" }, this.getTitle(object), this.safeObjectLink({
+	      className: "objectLeftBrace"
+	    }, " { "), ...propsArray, this.safeObjectLink({
+	      className: "objectRightBrace"
 	    }, " }"));
 	  })
 	});
 	
 	// Registration
 	function supportsObject(object, type) {
 	  if (!isGrip(object)) {
 	    return false;
@@ -2232,23 +2259,27 @@ return /******/ (function(modules) { // 
 	    objectLink: React.PropTypes.func
 	  },
 	
 	  getSource: function (grip) {
 	    return grip.displayString;
 	  },
 	
 	  render: wrapRender(function () {
-	    let grip = this.props.object;
-	    let objectLink = this.props.objectLink || span;
-	
-	    return span({ className: "objectBox objectBox-regexp" }, objectLink({
-	      object: grip,
-	      className: "regexpSource"
-	    }, this.getSource(grip)));
+	    let { object } = this.props;
+	    let objectLink = (config, ...children) => {
+	      if (this.props.objectLink) {
+	        return this.props.objectLink(Object.assign({ object }, config), ...children);
+	      }
+	      return span(config, ...children);
+	    };
+	
+	    return objectLink({
+	      className: "objectBox objectBox-regexp regexpSource"
+	    }, this.getSource(object));
 	  })
 	});
 	
 	// Registration
 	
 	function supportsObject(object, type) {
 	  if (!isGrip(object)) {
 	    return false;
@@ -2470,17 +2501,22 @@ return /******/ (function(modules) { // 
 	      object,
 	      mode,
 	      attachedActorIds,
 	      onDOMNodeMouseOver,
 	      onDOMNodeMouseOut,
 	      onInspectIconClick
 	    } = this.props;
 	    let elements = this.getElements(object, mode);
-	    let objectLink = this.props.objectLink || span;
+	    let objectLink = (config, ...children) => {
+	      if (this.props.objectLink) {
+	        return this.props.objectLink(Object.assign({ object }, config), ...children);
+	      }
+	      return span(config, ...children);
+	    };
 	
 	    let isInTree = attachedActorIds ? attachedActorIds.includes(object.actor) : true;
 	
 	    let baseConfig = { className: "objectBox objectBox-node" };
 	    let inspectIcon;
 	    if (isInTree) {
 	      if (onDOMNodeMouseOver) {
 	        Object.assign(baseConfig, {
@@ -2500,17 +2536,17 @@ return /******/ (function(modules) { // 
 	          draggable: false,
 	          // TODO: Localize this with "openNodeInInspector" when Bug 1317038 lands
 	          title: "Click to select the node in the inspector",
 	          onClick: e => onInspectIconClick(object, e)
 	        });
 	      }
 	    }
 	
-	    return span(baseConfig, objectLink({ object }, ...elements), inspectIcon);
+	    return span(baseConfig, objectLink({}, ...elements), inspectIcon);
 	  })
 	});
 	
 	// Registration
 	function supportsObject(object, type) {
 	  if (!isGrip(object)) {
 	    return false;
 	  }
@@ -2859,18 +2895,24 @@ return /******/ (function(modules) { // 
 	      /*
 	       * Since Reps are used in the JSON Viewer, we can't localize
 	       * the "Stack trace" label (defined in debugger.properties as
 	       * "variablesViewErrorStacktrace" property), until Bug 1317038 lands.
 	       */
 	      content = `${content}\nStack trace:\n${preview.stack}`;
 	    }
 	
-	    let objectLink = this.props.objectLink || span;
-	    return objectLink({ object, className: "objectBox-stackTrace" }, span({}, content));
+	    let objectLink = (config, ...children) => {
+	      if (this.props.objectLink) {
+	        return this.props.objectLink(Object.assign({ object }, config), ...children);
+	      }
+	      return span(config, ...children);
+	    };
+	
+	    return objectLink({ className: "objectBox-stackTrace" }, content);
 	  })
 	});
 	
 	// Registration
 	function supportsObject(object, type) {
 	  if (!isGrip(object)) {
 	    return false;
 	  }
@@ -3132,24 +3174,22 @@ return /******/ (function(modules) { // 
 	    if (!grip.preview) {
 	      return 0;
 	    }
 	
 	    return grip.preview.length || grip.preview.childNodesLength || 0;
 	  },
 	
 	  getTitle: function (object, context) {
-	    let objectLink = this.props.objectLink || span;
-	    if (this.props.mode !== MODE.TINY) {
-	      let title = this.props.title || object.class || "Array";
-	      return objectLink({
-	        object: object
-	      }, title, " ");
+	    if (this.props.mode === MODE.TINY) {
+	      return "";
 	    }
-	    return "";
+	
+	    let title = this.props.title || object.class || "Array";
+	    return this.safeObjectLink({}, title + " ");
 	  },
 	
 	  getPreviewItems: function (grip) {
 	    if (!grip.preview) {
 	      return null;
 	    }
 	
 	    return grip.preview.items || grip.preview.childNodes || null;
@@ -3192,28 +3232,39 @@ return /******/ (function(modules) { // 
 	          object: exc,
 	          delim: delim,
 	          // Do not propagate title to array items reps
 	          title: undefined
 	        })));
 	      }
 	    }
 	    if (previewItems.length > max || gripLength > previewItems.length) {
-	      let objectLink = this.props.objectLink || span;
 	      let leftItemNum = gripLength - max > 0 ? gripLength - max : gripLength - previewItems.length;
 	      items.push(Caption({
-	        object: objectLink({
-	          object: this.props.object
-	        }, leftItemNum + " more…")
+	        object: this.safeObjectLink({}, leftItemNum + " more…")
 	      }));
 	    }
 	
 	    return items;
 	  },
 	
+	  safeObjectLink: function (config, ...children) {
+	    if (this.props.objectLink) {
+	      return this.props.objectLink(Object.assign({
+	        object: this.props.object
+	      }, config), ...children);
+	    }
+	
+	    if (Object.keys(config).length === 0 && children.length === 1) {
+	      return children[0];
+	    }
+	
+	    return span(config, ...children);
+	  },
+	
 	  render: wrapRender(function () {
 	    let {
 	      object,
 	      mode = MODE.SHORT
 	    } = this.props;
 	
 	    let items;
 	    let brackets;
@@ -3227,42 +3278,39 @@ return /******/ (function(modules) { // 
 	      items = [span({ className: "length" }, isEmpty ? "" : objectLength)];
 	      brackets = needSpace(false);
 	    } else {
 	      let max = mode === MODE.SHORT ? 3 : 10;
 	      items = this.arrayIterator(object, max);
 	      brackets = needSpace(items.length > 0);
 	    }
 	
-	    let objectLink = this.props.objectLink || span;
 	    let title = this.getTitle(object);
 	
 	    return span({
-	      className: "objectBox objectBox-array" }, title, objectLink({
-	      className: "arrayLeftBracket",
-	      object: object
-	    }, brackets.left), ...items, objectLink({
-	      className: "arrayRightBracket",
-	      object: object
+	      className: "objectBox objectBox-array" }, title, this.safeObjectLink({
+	      className: "arrayLeftBracket"
+	    }, brackets.left), ...items, this.safeObjectLink({
+	      className: "arrayRightBracket"
 	    }, brackets.right), span({
 	      className: "arrayProperties",
 	      role: "group" }));
 	  })
 	});
 	
 	/**
 	 * Renders array item. Individual values are separated by
 	 * a delimiter (a comma by default).
 	 */
 	let GripArrayItem = React.createFactory(React.createClass({
 	  displayName: "GripArrayItem",
 	
 	  propTypes: {
 	    delim: React.PropTypes.string,
-	    object: React.PropTypes.object.isRequired,
+	    object: React.PropTypes.oneOfType([React.PropTypes.object, React.PropTypes.number, React.PropTypes.string]).isRequired,
 	    objectLink: React.PropTypes.func,
 	    // @TODO Change this to Object.values once it's supported in Node's version of V8
 	    mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
 	    provider: React.PropTypes.object,
 	    attachedActorIds: React.PropTypes.array,
 	    onDOMNodeMouseOver: React.PropTypes.func,
 	    onDOMNodeMouseOut: React.PropTypes.func,
 	    onInspectIconClick: React.PropTypes.func
@@ -3323,22 +3371,17 @@ return /******/ (function(modules) { // 
 	    onDOMNodeMouseOver: React.PropTypes.func,
 	    onDOMNodeMouseOut: React.PropTypes.func,
 	    onInspectIconClick: React.PropTypes.func,
 	    title: React.PropTypes.string
 	  },
 	
 	  getTitle: function (object) {
 	    let title = this.props.title || (object && object.class ? object.class : "Map");
-	    if (this.props.objectLink) {
-	      return this.props.objectLink({
-	        object: object
-	      }, title);
-	    }
-	    return title;
+	    return this.safeObjectLink({}, title);
 	  },
 	
 	  safeEntriesIterator: function (object, max) {
 	    max = typeof max === "undefined" ? 3 : max;
 	    try {
 	      return this.entriesIterator(object, max);
 	    } catch (err) {
 	      console.error(err);
@@ -3360,23 +3403,19 @@ return /******/ (function(modules) { // 
 	      indexes = indexes.concat(this.getEntriesIndexes(mapEntries, max - indexes.length, (t, value, name) => {
 	        return !isInterestingEntry(t, value, name);
 	      }));
 	    }
 	
 	    let entries = this.getEntries(mapEntries, indexes);
 	    if (entries.length < mapEntries.length) {
 	      // There are some undisplayed entries. Then display "more…".
-	      let objectLink = this.props.objectLink || span;
-	
 	      entries.push(Caption({
 	        key: "more",
-	        object: objectLink({
-	          object: object
-	        }, `${mapEntries.length - max} more…`)
+	        object: this.safeObjectLink({}, `${mapEntries.length - max} more…`)
 	      }));
 	    }
 	
 	    return entries;
 	  },
 	
 	  /**
 	   * Get entries ordered by index.
@@ -3441,34 +3480,42 @@ return /******/ (function(modules) { // 
 	          indexes.push(i);
 	        }
 	      }
 	
 	      return indexes;
 	    }, []);
 	  },
 	
+	  safeObjectLink: function (config, ...children) {
+	    if (this.props.objectLink) {
+	      return this.props.objectLink(Object.assign({
+	        object: this.props.object
+	      }, config), ...children);
+	    }
+	
+	    if (Object.keys(config).length === 0 && children.length === 1) {
+	      return children[0];
+	    }
+	
+	    return span(config, ...children);
+	  },
+	
 	  render: wrapRender(function () {
 	    let object = this.props.object;
 	    let propsArray = this.safeEntriesIterator(object, this.props.mode === MODE.LONG ? 10 : 3);
 	
-	    let objectLink = this.props.objectLink || span;
 	    if (this.props.mode === MODE.TINY) {
-	      return span({ className: "objectBox objectBox-object" }, this.getTitle(object), objectLink({
-	        className: "objectLeftBrace",
-	        object: object
-	      }, ""));
+	      return span({ className: "objectBox objectBox-object" }, this.getTitle(object));
 	    }
 	
-	    return span({ className: "objectBox objectBox-object" }, this.getTitle(object), objectLink({
-	      className: "objectLeftBrace",
-	      object: object
-	    }, " { "), propsArray, objectLink({
-	      className: "objectRightBrace",
-	      object: object
+	    return span({ className: "objectBox objectBox-object" }, this.getTitle(object), this.safeObjectLink({
+	      className: "objectLeftBrace"
+	    }, " { "), propsArray, this.safeObjectLink({
+	      className: "objectRightBrace"
 	    }, " }"));
 	  })
 	});
 	
 	function supportsObject(grip, type) {
 	  if (!isGrip(grip)) {
 	    return false;
 	  }
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_array.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_array.html
@@ -9,18 +9,18 @@ Test ArrayRep rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - ArrayRep</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript;version=1.8"></script>
-<script type="application/javascript;version=1.8">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
 "use strict";
 /* import-globals-from head.js */
 
 window.onload = Task.async(function* () {
   const { REPS, MODE } = browserRequire("devtools/client/shared/components/reps/reps");
   let { Rep, ArrayRep } = REPS;
 
   let componentUnderTest = ArrayRep;
@@ -37,16 +37,17 @@ window.onload = Task.async(function* () 
     yield testMoreThanShortMaxProps();
     yield testMoreThanLongMaxProps();
     yield testRecursiveArray();
 
     // Test that properties are rendered as expected by ItemRep
     yield testNested();
 
     yield testArray();
+    yield testObjectLink();
   } catch (e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
   function testBasic() {
     // Test that correct rep is chosen
@@ -243,13 +244,49 @@ window.onload = Task.async(function* () 
       {
         mode: MODE.LONG,
         expectedOutput: defaultOutput,
       }
     ];
 
     testRepRenderModes(modeTests, "testNested", componentUnderTest, stub);
   }
+
+  function testObjectLink() {
+    let stub = ["a", "b", "c"];
+
+    const defaultOutput = `*[ *"a", "b", "c"* ]*`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: MODE.TINY,
+        expectedOutput: `*[*3*]*`,
+      },
+      {
+        mode: MODE.SHORT,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: MODE.LONG,
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(
+      modeTests,
+      "testObjectLink",
+      componentUnderTest,
+      stub,
+      {
+        objectLink: (props, ...children) => React.DOM.span({},
+          "*", ...children, "*"),
+      }
+    );
+  }
 });
 </script>
 </pre>
 </body>
 </html>
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_attribute.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_attribute.html
@@ -9,48 +9,65 @@ Test Attribute rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - Attribute</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript;version=1.8"></script>
-<script type="application/javascript;version=1.8">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
 window.onload = Task.async(function* () {
+  const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
+  let { Rep, Attribute } = REPS;
+
   try {
-    const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
-    let { Rep, Attribute } = REPS;
+    testBasic();
+    testObjectLink();
+  } catch (e) {
+    ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
+  } finally {
+    SimpleTest.finish();
+  }
+
+  function testBasic() {
+    // Test that correct rep is chosen
+    const renderedRep = shallowRenderComponent(Rep, { object: getStub() });
+    is(renderedRep.type, Attribute.rep, `Rep correctly selects ${Attribute.rep.displayName}`);
 
-    let gripStub = {
+    // Test rendering
+    const renderedComponent = renderComponent(Attribute.rep, { object: getStub() });
+    is(renderedComponent.textContent, "class=\"autocomplete-suggestions\"", "Attribute rep has expected text content");
+  }
+
+  function testObjectLink() {
+    const renderedComponent = renderComponent(Attribute.rep, {
+      object: getStub(),
+      objectLink: (props, ...children) => React.DOM.span({},
+        "*", ...children, "*"),
+    });
+    is(renderedComponent.textContent, "*class=\"autocomplete-suggestions\"*",
+      "Attribute rep has expected text content when an objectLink is passed as a prop");
+  }
+
+  function getStub() {
+    return {
       "type": "object",
       "class": "Attr",
       "actor": "server1.conn19.obj65",
       "extensible": true,
       "frozen": false,
       "sealed": false,
       "ownPropertyLength": 0,
       "preview": {
         "kind": "DOMNode",
         "nodeType": 2,
         "nodeName": "class",
         "value": "autocomplete-suggestions"
       }
     };
-
-    // Test that correct rep is chosen
-    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
-    is(renderedRep.type, Attribute.rep, `Rep correctly selects ${Attribute.rep.displayName}`);
-
-    // Test rendering
-    const renderedComponent = renderComponent(Attribute.rep, { object: gripStub });
-    is(renderedComponent.textContent, "class=\"autocomplete-suggestions\"", "Attribute rep has expected text content");
-  } catch(e) {
-    ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
-  } finally {
-    SimpleTest.finish();
   }
 });
 </script>
 </pre>
 </body>
 </html>
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_comment-node.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_comment-node.html
@@ -9,18 +9,18 @@ Test comment-node rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - comment-node</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript;version=1.8"></script>
-<script type="application/javascript;version=1.8">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
 "use strict";
 
 window.onload = Task.async(function* () {
   try {
     const { REPS, MODE } = browserRequire("devtools/client/shared/components/reps/reps");
     let { Rep, CommentNode } = REPS;
 
     let gripStub = {
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_date-time.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_date-time.html
@@ -9,25 +9,26 @@ Test DateTime rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - DateTime</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript;version=1.8"></script>
-<script type="application/javascript;version=1.8">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
 window.onload = Task.async(function* () {
   const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
   let { Rep, DateTime } = REPS;
 
   try {
     testValid();
     testInvalid();
+    testObjectLink();
   } catch(e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
   function testValid() {
     let gripStub = {
@@ -67,13 +68,37 @@ window.onload = Task.async(function* () 
         }
       }
     };
 
     // Test rendering
     const renderedComponent = renderComponent(DateTime.rep, { object: gripStub });
     is(renderedComponent.textContent, "Invalid Date", "DateTime rep has expected text content for invalid date");
   }
+
+  function testObjectLink() {
+    let gripStub = {
+      "type": "object",
+      "class": "Date",
+      "actor": "server1.conn0.child1/obj32",
+      "extensible": true,
+      "frozen": false,
+      "sealed": false,
+      "ownPropertyLength": 0,
+      "preview": {
+        "timestamp": 1459372644859
+      }
+    };
+
+    // Test rendering
+    const renderedComponent = renderComponent(DateTime.rep, {
+      object: gripStub,
+      objectLink: (props, ...children) => React.DOM.span({},
+        "*", ...children, "*"),
+    });
+    is(renderedComponent.textContent, "*Date *2016-03-30T21:17:24.859Z",
+      "DateTime rep has expected text content when an objectLink is passed as a prop");
+  }
 });
 </script>
 </pre>
 </body>
 </html>
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_document.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_document.html
@@ -9,48 +9,67 @@ Test Document rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - Document</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript;version=1.8"></script>
-<script type="application/javascript;version=1.8">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
 window.onload = Task.async(function* () {
   const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
   let { Rep, Document } = REPS;
 
   try {
-    let gripStub = {
+    testBasic();
+    testObjectLink();
+  } catch (e) {
+    ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
+  } finally {
+    SimpleTest.finish();
+  }
+
+  function testBasic() {
+    // Test that correct rep is chosen
+    const renderedRep = shallowRenderComponent(Rep, { object: getStub() });
+    is(renderedRep.type, Document.rep, `Rep correctly selects ${Document.rep.displayName}`);
+
+    // Test rendering
+    const renderedComponent = renderComponent(Document.rep, { object: getStub() });
+    is(renderedComponent.textContent, "https://www.mozilla.org/en-US/firefox/new/",
+      "Document rep has expected text content");
+  }
+
+  function testObjectLink() {
+    // Test rendering
+    const renderedComponent = renderComponent(Document.rep, {
+      object: getStub(),
+      objectLink: (props, ...children) => React.DOM.span({},
+        "*", ...children, "*"),
+    });
+    is(renderedComponent.textContent, "*HTMLDocument *https://www.mozilla.org/en-US/firefox/new/",
+      "Document rep has expected text content when an objectLink is passed as a prop");
+  }
+
+  function getStub() {
+    return {
       "type": "object",
       "class": "HTMLDocument",
       "actor": "server1.conn17.obj115",
       "extensible": true,
       "frozen": false,
       "sealed": false,
       "ownPropertyLength": 1,
       "preview": {
         "kind": "DOMNode",
         "nodeType": 9,
         "nodeName": "#document",
         "location": "https://www.mozilla.org/en-US/firefox/new/"
       }
     };
-
-    // Test that correct rep is chosen
-    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
-    is(renderedRep.type, Document.rep, `Rep correctly selects ${Document.rep.displayName}`);
-
-    // Test rendering
-    const renderedComponent = renderComponent(Document.rep, { object: gripStub });
-    is(renderedComponent.textContent, "https://www.mozilla.org/en-US/firefox/new/", "Document rep has expected text content");
-  } catch(e) {
-    ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
-  } finally {
-    SimpleTest.finish();
   }
 });
 </script>
 </pre>
 </body>
 </html>
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_element-node.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_element-node.html
@@ -9,18 +9,18 @@ Test Element node rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - Element node</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript;version=1.8"></script>
-<script type="application/javascript;version=1.8">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
 "use strict";
 
 window.onload = Task.async(function* () {
   const {
     REPS,
     MODE,
     getSelectableInInspectorGrips,
   } = browserRequire("devtools/client/shared/components/reps/reps");
@@ -34,16 +34,17 @@ window.onload = Task.async(function* () 
     yield testNodeWithoutAttributes();
     yield testLotsOfAttributes();
     yield testSvgNode();
     yield testSvgNodeInXHTML();
 
     yield testOnMouseOver();
     yield testOnMouseOut();
     yield testOnInspectIconClick();
+    yield testObjectLink();
   } catch (e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
   function testBodyNode() {
     const stub = getGripStub("testBodyNode");
@@ -267,16 +268,39 @@ window.onload = Task.async(function* () 
     TestUtils.Simulate.click(inspectIconNode);
 
     is(inspectIconClickedValue, grips[0],
       "onInspectIconClick is called with expected value when inspect icon is clicked");
     ok(inspectIconClickedEvent !== null && inspectIconClickedEvent.type === "click",
       "onInspectIconClick forwarded the original event to the callback");
   }
 
+  function testObjectLink() {
+    const stub = getGripStub("testBodyNode");
+
+    const renderedComponent = renderComponent(ElementNode.rep, {
+      object: stub,
+      objectLink: (props, ...children) => React.DOM.span({},
+        "*", ...children, "*"),
+    });
+    is(renderedComponent.textContent, `*<body id="body-id" class="body-class">*`,
+      "Element node rep has expected text content for body node when an objectLink is " +
+      "passed as a prop");
+
+    const tinyRenderedComponent = renderComponent(ElementNode.rep, {
+      object: stub, mode: MODE.TINY,
+      objectLink: (props, ...children) => React.DOM.span({
+        className: "object-link"
+      }, "*", ...children, "*"),
+    });
+    is(tinyRenderedComponent.textContent, `*body#body-id.body-class*`,
+      "Element node rep has expected text content for body node in tiny mode when an " +
+      "objectLink is passed as a prop");
+  }
+
   function getGripStub(name) {
     switch (name) {
       case "testBodyNode":
         return {
           "type": "object",
           "actor": "server1.conn1.child1/obj30",
           "class": "HTMLBodyElement",
           "ownPropertyLength": 0,
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_error.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_error.html
@@ -9,18 +9,18 @@ Test Error rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - Error</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript;version=1.8"></script>
-<script type="application/javascript;version=1.8">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
 "use strict";
 
 window.onload = Task.async(function* () {
   const { REPS, MODE } = browserRequire("devtools/client/shared/components/reps/reps");
   let { Rep, ErrorRep } = REPS;
 
   try {
     // Test errors with different properties
@@ -31,16 +31,18 @@ window.onload = Task.async(function* () 
     // Test different kind of errors
     yield testEvalError();
     yield testInternalError();
     yield testRangeError();
     yield testReferenceError();
     yield testSyntaxError();
     yield testTypeError();
     yield testURIError();
+
+    yield testObjectLink();
   } catch (e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
   function testSimpleError() {
     // Test object = `new Error("Error message")`
@@ -255,16 +257,44 @@ window.onload = Task.async(function* () 
 
     const tinyRenderedComponent = renderComponent(
       ErrorRep.rep, {object: stub, mode: MODE.TINY});
     is(tinyRenderedComponent.textContent,
       "URIError",
       "Error Rep has expected text content for URIError in tiny mode");
   }
 
+  function testObjectLink() {
+    // Test object = `new Error("Error message")`
+    const stub = getGripStub("testSimpleError");
+
+    const renderedComponent = renderComponent(ErrorRep.rep, {
+      object: stub,
+      objectLink: (props, ...children) => React.DOM.span({},
+        "*", ...children, "*"),
+    });
+    is(renderedComponent.textContent,
+      "*Error: Error message\n" +
+      "Stack trace:\n" +
+      "@debugger eval code:1:13\n*",
+      "Error Rep has expected text content when an objectLink is passed as a prop");
+
+    const tinyRenderedComponent = renderComponent(
+      ErrorRep.rep, {
+        object: stub,
+        mode: MODE.TINY,
+        objectLink: (props, ...children) => React.DOM.span({},
+          "*", ...children, "*"),
+      });
+    is(tinyRenderedComponent.textContent,
+      "*Error*",
+      "Error Rep has expected text content in tiny mode when an objectLink is passed " +
+      "as a prop");
+  }
+
   function getGripStub(name) {
     switch (name) {
       case "testSimpleError":
         return {
           "type": "object",
           "actor": "server1.conn1.child1/obj1020",
           "class": "Error",
           "ownPropertyLength": 4,
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_event.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_event.html
@@ -9,18 +9,18 @@ Test Event rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - Event</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript;version=1.8"></script>
-<script type="application/javascript;version=1.8">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
 window.onload = Task.async(function* () {
   const {
     REPS,
     MODE,
     getSelectableInInspectorGrips,
   } = browserRequire("devtools/client/shared/components/reps/reps");
   let { Rep, Event } = REPS;
 
@@ -33,16 +33,18 @@ window.onload = Task.async(function* () 
     yield testMouseEvent();
     yield testKeyboardEvent();
     yield testKeyboardEventWithModifiers();
     yield testMessageEvent();
 
     yield testOnDomNodeMouseOver();
     yield testOnDomNodeMouseOut();
     yield testOnDomNodeInspectIconClick();
+
+    yield testObjectLink();
   } catch (e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
   function testEvent() {
     const renderedComponent = renderComponent(Event.rep, {
@@ -205,16 +207,39 @@ window.onload = Task.async(function* () 
 
     TestUtils.Simulate.click(icon);
 
     is(inspectIconClickedValue, grips[0],
       "onInspectIconClick is called with the expected argument " +
       "when the inspect icon is clicked");
   }
 
+  function testObjectLink() {
+    const renderedComponent = renderComponent(Event.rep, {
+      object: getGripStub("testMouseEvent"),
+      objectLink: (props, ...children) => React.DOM.span({},
+        "*", ...children, "*"),
+    });
+    is(renderedComponent.textContent,
+      "*click** { *target: div#test, clientX: 62, clientY: 18, *3 more…** }*",
+      "Event rep has expected text content when an objectLink is passed as a prop");
+
+    const longRenderedComponent = renderComponent(Event.rep, {
+      object: getGripStub("testMouseEvent"),
+      mode: MODE.LONG,
+      objectLink: (props, ...children) => React.DOM.span({},
+        "*", ...children, "*"),
+    });
+    is(longRenderedComponent.textContent,
+      "*click** { *target: div#test, buttons: 0, clientX: 62, clientY: 18, layerX: 0, " +
+      "layerY: 0* }*",
+      "Event rep has expected text content in long mode when an objectLink is passed " +
+      "as a prop");
+  }
+
   function getGripStub(name) {
     switch (name) {
       case "testEvent":
         return {
           "type": "object",
           "class": "Event",
           "actor": "server1.conn23.obj35",
           "extensible": true,
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_failure.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_failure.html
@@ -9,18 +9,18 @@ Test fallback for rep rendering when a r
 <head>
   <meta charset="utf-8">
   <title>Rep test - Failure</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript;version=1.8"></script>
-<script type="application/javascript;version=1.8">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
 window.onload = Task.async(function* () {
   try {
     const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
     let { Rep, ArrayRep, RegExp } = REPS;
 
     // Force the RegExp rep to crash by creating RegExp grip that throws when accessing
     // the displayString property
     let gripStub = {
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_function.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_function.html
@@ -9,18 +9,18 @@ Test Func rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - Func</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript;version=1.8"></script>
-<script type="application/javascript;version=1.8">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
 window.onload = Task.async(function* () {
   const { REPS, MODE } = browserRequire("devtools/client/shared/components/reps/reps");
   let { Rep, Func } = REPS;
 
   const componentUnderTest = Func;
 
   try {
     // Test that correct rep is chosen
@@ -31,16 +31,17 @@ window.onload = Task.async(function* () 
     yield testNamed();
     yield testVarNamed();
     yield testAnon();
     yield testLongName();
     yield testAsyncFunction();
     yield testAnonAsyncFunction();
     yield testGeneratorFunction();
     yield testAnonGeneratorFunction();
+    yield testObjectLink();
   } catch(e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
   function testNamed() {
     // Test declaration: `function testName() { let innerVar = "foo" }`
@@ -221,16 +222,37 @@ window.onload = Task.async(function* () 
       {
         mode: MODE.LONG,
         expectedOutput: defaultOutput,
       }
     ];
     testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
   }
 
+  function testObjectLink() {
+    // Test declaration: `function testName() { let innerVar = "foo" }`
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: "*function *testName()",
+      }
+    ];
+
+    testRepRenderModes(
+      modeTests,
+      "testObjectLink",
+      componentUnderTest,
+      getGripStub("testNamed"),
+      {
+        objectLink: (props, ...children) => React.DOM.span({},
+          "*", ...children, "*"),
+      }
+    );
+  }
+
   function getGripStub(functionName) {
     switch (functionName) {
       case "testNamed":
         return {
           "type": "object",
           "class": "Function",
           "actor": "server1.conn6.obj35",
           "extensible": true,
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_grip-array.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_grip-array.html
@@ -9,18 +9,18 @@ Test GripArray rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - GripArray</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript;version=1.8"></script>
-<script type="application/javascript;version=1.8">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
 window.onload = Task.async(function* () {
   const {
     REPS,
     MODE,
     getSelectableInInspectorGrips,
   } = browserRequire("devtools/client/shared/components/reps/reps");
   let { Rep, GripArray } = REPS;
 
@@ -41,16 +41,18 @@ window.onload = Task.async(function* () 
     yield testPreviewLimit();
     yield testNamedNodeMap();
     yield testNodeList();
     yield testDocumentFragment();
 
     yield testOnDomNodeMouseOver();
     yield testOnDomNodeMouseOut();
     yield testOnDomNodeInspectIconClick();
+
+    yield testObjectLink();
   } catch (e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
   function testBasic() {
     // Test array: `[]`
@@ -409,16 +411,51 @@ window.onload = Task.async(function* () 
       TestUtils.Simulate.click(icon);
 
       is(inspectIconClickedValue, grips[index],
         "onInspectIconClick is called with the expected argument " +
         "when the inspect icon is clicked");
     });
   }
 
+  function testObjectLink() {
+    // Test array = `["test string"…] //4 items`
+    const defaultOutput = `*Array **[ *${Array(maxLength.short).fill("\"test string\"").join(", ")}, *1 more…** ]*`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: MODE.TINY,
+        expectedOutput: `*[*${maxLength.short + 1}*]*`,
+      },
+      {
+        mode: MODE.SHORT,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: MODE.LONG,
+        expectedOutput: `*Array **[ *${Array(maxLength.short + 1).fill("\"test string\"").join(", ")}* ]*`,
+      }
+    ];
+
+    testRepRenderModes(
+      modeTests,
+      "testObjectLink",
+      componentUnderTest,
+      getGripStub("testMoreThanShortMaxProps"),
+      {
+        objectLink: (props, ...children) => React.DOM.span({},
+          "*", ...children, "*"),
+      }
+    );
+  }
+
   function getGripStub(functionName) {
     switch (functionName) {
       case "testBasic":
         return {
           "type": "object",
           "class": "Array",
           "actor": "server1.conn0.obj35",
           "extensible": true,
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_grip-map.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_grip-map.html
@@ -9,18 +9,18 @@ Test GripMap rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - GripMap</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript;version=1.8"></script>
-<script type="application/javascript;version=1.8">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
 "use strict";
 
 window.onload = Task.async(function* () {
   const {
     REPS,
     MODE,
     getSelectableInInspectorGrips,
   } = browserRequire("devtools/client/shared/components/reps/reps");
@@ -36,16 +36,18 @@ window.onload = Task.async(function* () 
     // // Test entries iterator
     yield testMaxEntries();
     yield testMoreThanMaxEntries();
     yield testUninterestingEntries();
 
     yield testOnDomNodeMouseOver();
     yield testOnDomNodeMouseOut();
     yield testOnDomNodeInspectIconClick();
+
+    yield testObjectLink();
   } catch (e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
   function testEmptyMap() {
     // Test object: `new Map()`
@@ -395,16 +397,55 @@ window.onload = Task.async(function* () 
 
     icons.forEach((icon, index) => {
       TestUtils.Simulate.click(icon);
       is(inspectIconClickedValue, keysGrips[index], "onInspectIconClick is called " +
         "with the expected argument when the inspect icon is clicked");
     });
   }
 
+  function testObjectLink() {
+    // Test object:
+    // `new Map([["key-a",null], ["key-b",undefined], ["key-c","value-c"], ["key-d",4]])`
+    const defaultOutput =
+      `*Map** { *"key-a": null, "key-c": "value-c", "key-d": 4, *1 more…** }*`;
+    const longOutput =
+      `*Map** { *"key-a": null, "key-b": undefined, "key-c": "value-c", "key-d": 4* }*`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: MODE.TINY,
+        expectedOutput: `*Map*`,
+      },
+      {
+        mode: MODE.SHORT,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: MODE.LONG,
+        expectedOutput: longOutput,
+      }
+    ];
+
+    testRepRenderModes(
+      modeTests,
+      "testObjectLink",
+      componentUnderTest,
+      getGripStub("testUninterestingEntries"),
+      {
+        objectLink: (props, ...children) => React.DOM.span({},
+          "*", ...children, "*"),
+      }
+    );
+  }
+
   function getGripStub(functionName) {
     switch (functionName) {
       case "testEmptyMap":
         return {
           "type": "object",
           "actor": "server1.conn1.child1/obj97",
           "class": "Map",
           "extensible": true,
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_grip.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_grip.html
@@ -9,18 +9,18 @@ Test grip rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - grip</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript;version=1.8"></script>
-<script type="application/javascript;version=1.8">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
 window.onload = Task.async(function* () {
   const {
     REPS,
     MODE,
     getSelectableInInspectorGrips,
   } = browserRequire("devtools/client/shared/components/reps/reps");
   let { Rep, Grip } = REPS;
 
@@ -47,16 +47,18 @@ window.onload = Task.async(function* () 
     yield testNestedArray();
 
     // Test that 'more' property doesn't clobber the caption.
     yield testMoreProp();
 
     yield testOnDomNodeMouseOver();
     yield testOnDomNodeMouseOut();
     yield testOnDomNodeInspectIconClick();
+
+    yield testObjectLink();
   } catch(e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
   function testBasic() {
     // Test object: `{}`
@@ -623,16 +625,52 @@ window.onload = Task.async(function* () 
     icons.forEach((icon, index) => {
       TestUtils.Simulate.click(icon);
       is(inspectIconClickedValue, grips[index],
         "onInspectIconClick is called with the expected argument " +
         "when the inspect icon is clicked");
     });
   }
 
+  function testObjectLink() {
+    // Test object: `{a: undefined, b: 1, more: 2, d: 3}`;
+    const defaultOutput = `*Object** { *b: 1, more: 2, d: 3, *1 more…** }*`;
+    const longOutput = `*Object** { *a: undefined, b: 1, more: 2, d: 3* }*`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: MODE.TINY,
+        expectedOutput: `*Object*`,
+      },
+      {
+        mode: MODE.SHORT,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: MODE.LONG,
+        expectedOutput: longOutput,
+      }
+    ];
+
+    testRepRenderModes(
+      modeTests,
+      "testObjectLink",
+      componentUnderTest,
+      getGripStub("testMoreProp"),
+      {
+        objectLink: (props, ...children) => React.DOM.span({},
+          "*", ...children, "*"),
+      }
+    );
+  }
+
   function getGripStub(functionName) {
     switch (functionName) {
       case "testBasic":
         return {
           "type": "object",
           "class": "Object",
           "actor": "server1.conn0.obj304",
           "extensible": true,
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_infinity.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_infinity.html
@@ -9,18 +9,18 @@ Test Infinity rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - Infinity</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript;version=1.8"></script>
-<script type="application/javascript;version=1.8">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
 "use strict";
 
 window.onload = Task.async(function* () {
   const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
   let { Rep, InfinityRep } = REPS;
 
   try {
     yield testInfinity();
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_long-string.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_long-string.html
@@ -9,18 +9,18 @@ Test LongString rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - LongString</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript;version=1.8"></script>
-<script type="application/javascript;version=1.8">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
 window.onload = Task.async(function* () {
   const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
   let { Rep, LongStringRep } = REPS;
 
   try {
     // Test that correct rep is chosen
     const renderedRep = shallowRenderComponent(Rep, { object: getGripStub("testMultiline") });
     is(renderedRep.type, LongStringRep.rep,
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_nan.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_nan.html
@@ -9,18 +9,18 @@ Test NaN rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - NaN</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript;version=1.8"></script>
-<script type="application/javascript;version=1.8">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
 "use strict";
 
 window.onload = Task.async(function* () {
   const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
   let { Rep, NaNRep } = REPS;
 
   try {
     yield testNaN();
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_null.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_null.html
@@ -9,18 +9,18 @@ Test Null rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - Null</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript;version=1.8"></script>
-<script type="application/javascript;version=1.8">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
 window.onload = Task.async(function* () {
   try {
     const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
     let { Rep, Null } = REPS;
 
     let gripStub = {
       "type": "null"
     };
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_number.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_number.html
@@ -9,18 +9,18 @@ Test Number rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - Number</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript;version=1.8"></script>
-<script type="application/javascript;version=1.8">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
 window.onload = Task.async(function* () {
   const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
   let { Rep, Number } = REPS;
 
   try {
     yield testInt();
     yield testBoolean();
     yield testNegativeZero();
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_object-with-text.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_object-with-text.html
@@ -9,18 +9,18 @@ Test ObjectWithText rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - ObjectWithText</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript;version=1.8"></script>
-<script type="application/javascript;version=1.8">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
 window.onload = Task.async(function* () {
   try {
     const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
     let { Rep, ObjectWithText } = REPS;
 
     let gripStub = {
       "type": "object",
       "class": "CSSStyleRule",
@@ -37,16 +37,27 @@ window.onload = Task.async(function* () 
 
     // Test that correct rep is chosen
     const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
     is(renderedRep.type, ObjectWithText.rep, `Rep correctly selects ${ObjectWithText.rep.displayName}`);
 
     // Test rendering
     const renderedComponent = renderComponent(ObjectWithText.rep, { object: gripStub });
     is(renderedComponent.textContent, "\".Shadow\"", "ObjectWithText rep has expected text content");
+
+    // Test rendering with objectLink
+    const objectLinkRenderedComponent = renderComponent(ObjectWithText.rep, {
+      object: gripStub,
+      objectLink: (props, ...children) => React.DOM.span({},
+        "*", ...children, "*"),
+    });
+    is(objectLinkRenderedComponent.textContent,
+      "*CSSStyleRule *\".Shadow\"",
+      "ObjectWithText rep has expected text content when an objectLink is passed as a prop"
+    );
   } catch(e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 });
 </script>
 </pre>
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_object-with-url.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_object-with-url.html
@@ -9,18 +9,18 @@ Test ObjectWithURL rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - ObjectWithURL</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript;version=1.8"></script>
-<script type="application/javascript;version=1.8">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
 window.onload = Task.async(function* () {
   try {
     let ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
     let React = browserRequire("devtools/client/shared/vendor/react");
 
     const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
     let { Rep, ObjectWithURL } = REPS;
 
@@ -43,16 +43,27 @@ window.onload = Task.async(function* () 
     is(renderedRep.type, ObjectWithURL.rep, `Rep correctly selects ${ObjectWithURL.rep.displayName}`);
 
     // Test rendering
     const renderedComponent = renderComponent(ObjectWithURL.rep, { object: gripStub });
     ok(renderedComponent.className.includes("objectBox-Location"), "ObjectWithURL rep has expected class name");
     const innerNode = renderedComponent.querySelector(".objectPropValue");
     is(innerNode.textContent, "https://www.mozilla.org/en-US/", "ObjectWithURL rep has expected inner HTML structure and text content");
 
+    // Test rendering with objectLink
+    const objectLinkRenderedComponent = renderComponent(ObjectWithURL.rep, {
+      object: gripStub,
+      objectLink: (props, ...children) => React.DOM.span({},
+        "*", ...children, "*"),
+    });
+    is(objectLinkRenderedComponent.textContent,
+      "*Location *https://www.mozilla.org/en-US/",
+      "ObjectWithURL rep has expected text content when an objectLink is passed as a prop"
+    );
+
     // @TODO test link once Bug 1245303 has been implemented.
   } catch(e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 });
 </script>
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_object.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_object.html
@@ -9,18 +9,18 @@ Test Obj rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - Obj</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript;version=1.8"></script>
-<script type="application/javascript;version=1.8">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
 window.onload = Task.async(function* () {
   const { REPS, MODE } = browserRequire("devtools/client/shared/components/reps/reps");
   let { Rep, Obj } = REPS;
 
   const componentUnderTest = Obj;
 
   try {
     yield testBasic();
@@ -36,16 +36,19 @@ window.onload = Task.async(function* () 
     // Test that properties are rendered as expected by PropRep
     yield testNested();
 
     // Test that 'more' property doesn't clobber the caption.
     yield testMoreProp();
 
     // Test that you can pass a custom title to the Rep
     yield testCustomTitle();
+
+    // Test that you can pass an objectLink to the Rep
+    yield testCustomTitle();
   } catch(e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
   function testBasic() {
     const stub = {};
@@ -280,13 +283,53 @@ window.onload = Task.async(function* () 
       modeTests,
       "testCustomTitle",
       componentUnderTest,
       stub, {
         title: customTitle
       }
     );
   }
+
+  function testObjectLink() {
+    const stub = {
+      a: undefined,
+      b: 1,
+      "more": 2,
+      d: 3
+    };
+    const defaultOutput = `*Object **{ *b: 1, more: 2, d: 3, *1 more…** }*`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: MODE.TINY,
+        expectedOutput: `*Object*`,
+      },
+      {
+        mode: MODE.SHORT,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: MODE.LONG,
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(
+      modeTests,
+      "testObjectLink",
+      componentUnderTest,
+      stub,
+      {
+        objectLink: (props, ...children) => React.DOM.span({},
+          "*", ...children, "*"),
+      }
+    );
+  }
 });
 </script>
 </pre>
 </body>
 </html>
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_promise.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_promise.html
@@ -9,18 +9,18 @@ Test Promise rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - Promise</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript;version=1.8"></script>
-<script type="application/javascript;version=1.8">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
 "use strict";
 
 window.onload = Task.async(function* () {
   const {
     REPS,
     MODE,
     getSelectableInInspectorGrips,
   } = browserRequire("devtools/client/shared/components/reps/reps");
@@ -33,16 +33,18 @@ window.onload = Task.async(function* () 
     yield testFulfilledWithNumber();
     yield testFulfilledWithString();
     yield testFulfilledWithObject();
     yield testFulfilledWithArray();
 
     yield testOnDomNodeMouseOver();
     yield testOnDomNodeMouseOut();
     yield testOnDomNodeInspectIconClick();
+
+    yield testObjectLink();
   } catch (e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
   function testPending() {
     // Test object = `new Promise((resolve, reject) => true)`
@@ -295,16 +297,54 @@ window.onload = Task.async(function* () 
     ok(icon !== null, "There is an inspect icon as expected");
 
     TestUtils.Simulate.click(icon);
     is(inspectIconClickedValues, grips[0],
       "onInspectIconClick is called with the expected argument " +
       "when the inspect icon is clicked");
   }
 
+  function testObjectLink() {
+    // Test object = `new Promise((resolve, reject) => true)`
+    const stub = getGripStub("testPending");
+
+    // Test rendering
+    const defaultOutput = `*Promise** { *<state>: "pending"* }*`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: MODE.TINY,
+        expectedOutput: `*Promise** { *"pending"* }*`,
+      },
+      {
+        mode: MODE.SHORT,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: MODE.LONG,
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(
+      modeTests,
+      "testObjectLink",
+      componentUnderTest,
+      stub,
+      {
+        objectLink: (props, ...children) => React.DOM.span({},
+          "*", ...children, "*"),
+      }
+    );
+  }
+
   function getGripStub(name) {
     switch (name) {
       case "testPending":
         return {
           "type": "object",
           "actor": "server1.conn1.child1/obj54",
           "class": "Promise",
           "promiseState": {
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_regexp.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_regexp.html
@@ -9,18 +9,18 @@ Test RegExp rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - RegExp</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript;version=1.8"></script>
-<script type="application/javascript;version=1.8">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
 window.onload = Task.async(function* () {
   try {
     const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
     let { Rep, RegExp } = REPS;
 
     let gripStub = {
       "type": "object",
       "class": "RegExp",
@@ -34,16 +34,28 @@ window.onload = Task.async(function* () 
 
     // Test that correct rep is chosen
     const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
     is(renderedRep.type, RegExp.rep, `Rep correctly selects ${RegExp.rep.displayName}`);
 
     // Test rendering
     const renderedComponent = renderComponent(RegExp.rep, { object: gripStub });
     is(renderedComponent.textContent, "/ab+c/i", "RegExp rep has expected text content");
+
+    // Test rendering with objectLink
+    const objectLinkRenderedComponent = renderComponent(RegExp.rep, {
+      object: gripStub,
+      objectLink: (props, ...children) => React.DOM.span({},
+        "*", ...children, "*"),
+    });
+    is(
+      objectLinkRenderedComponent.textContent,
+      "*/ab+c/i*",
+      "RegExp rep has expected text content when an objectLink is passed as a prop"
+    );
   } catch(e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 });
 </script>
 </pre>
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_string.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_string.html
@@ -9,18 +9,18 @@ Test String rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - String</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript;version=1.8"></script>
-<script type="application/javascript;version=1.8">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
 window.onload = Task.async(function* () {
   const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
   let { Rep, StringRep } = REPS;
 
   const test_cases = [{
     name: "testMultiline",
     props: {
       object: "aaaaaaaaaaaaaaaaaaaaa\nbbbbbbbbbbbbbbbbbbb\ncccccccccccccccc\n",
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_stylesheet.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_stylesheet.html
@@ -9,18 +9,18 @@ Test Stylesheet rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - Stylesheet</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript;version=1.8"></script>
-<script type="application/javascript;version=1.8">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
 window.onload = Task.async(function* () {
   try {
     const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
     let { Rep, StyleSheet } = REPS;
 
     let gripStub = {
       "type": "object",
       "class": "CSSStyleSheet",
@@ -37,16 +37,28 @@ window.onload = Task.async(function* () 
 
     // Test that correct rep is chosen
     const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
     is(renderedRep.type, StyleSheet.rep, `Rep correctly selects ${StyleSheet.rep.displayName}`);
 
     // Test rendering
     const renderedComponent = renderComponent(StyleSheet.rep, { object: gripStub });
     is(renderedComponent.textContent, "StyleSheet https://example.com/styles.css", "StyleSheet rep has expected text content");
+
+    // Test rendering with objectLink
+    const objectLinkRenderedComponent = renderComponent(StyleSheet.rep, {
+      object: gripStub,
+      objectLink: (props, ...children) => React.DOM.span({},
+        "*", ...children, "*"),
+    });
+    is(
+      objectLinkRenderedComponent.textContent,
+      "*StyleSheet *https://example.com/styles.css",
+      "StyleSheet rep has expected text content when an objectLink is passed as a prop"
+    );
   } catch(e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 });
 </script>
 </pre>
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_symbol.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_symbol.html
@@ -9,18 +9,18 @@ Test Symbol rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - String</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript;version=1.8"></script>
-<script type="application/javascript;version=1.8">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
 "use strict";
 /* import-globals-from head.js */
 
 window.onload = Task.async(function* () {
   const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
   let { Rep, SymbolRep } = REPS;
 
   let gripStubs = new Map();
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_text-node.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_text-node.html
@@ -9,18 +9,18 @@ Test text-node rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - text-node</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript;version=1.8"></script>
-<script type="application/javascript;version=1.8">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
 "use strict";
 
 window.onload = Task.async(function* () {
   const {
     REPS,
     MODE,
     getSelectableInInspectorGrips,
   } = browserRequire("devtools/client/shared/components/reps/reps");
@@ -58,16 +58,18 @@ window.onload = Task.async(function* () 
       `Rep correctly selects ${TextNode.rep.displayName}`);
 
     yield testRendering();
     yield testRenderingWithEOL();
 
     yield testOnMouseOver();
     yield testOnMouseOut();
     yield testOnInspectIconClick();
+
+    yield testObjectLink();
   } catch (e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
   function testRendering() {
     const stub = gripStubs.get("testRendering");
@@ -197,13 +199,48 @@ window.onload = Task.async(function* () 
     ok(inspectIconNode !== null, "There is an inspect icon as expected");
     TestUtils.Simulate.click(inspectIconNode);
 
     is(inspectIconClickedValue, grips[0],
       "onInspectIconClick is called with expected value when inspect icon is clicked");
     ok(inspectIconClickedEvent !== null && inspectIconClickedEvent.type === "click",
       "onInspectIconClick forwarded the original event to the callback");
   }
+
+  function testObjectLink() {
+    const stub = gripStubs.get("testRendering");
+    const defaultOutput = `*#text* "hello world"`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: MODE.TINY,
+        expectedOutput: "*#text*",
+      },
+      {
+        mode: MODE.SHORT,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: MODE.LONG,
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(
+      modeTests,
+      "testObjectLink",
+      TextNode,
+      stub,
+      {
+        objectLink: (props, ...children) => React.DOM.span({},
+          "*", ...children, "*"),
+      }
+    );
+  }
 });
 </script>
 </pre>
 </body>
 </html>
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_undefined.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_undefined.html
@@ -9,18 +9,18 @@ Test undefined rep
 <head>
   <meta charset="utf-8">
   <title>Rep test - undefined</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript;version=1.8"></script>
-<script type="application/javascript;version=1.8">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
 window.onload = Task.async(function* () {
   try {
     let ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
     let React = browserRequire("devtools/client/shared/vendor/react");
     const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
     let { Rep, Undefined } = REPS;
 
     let gripStub = {
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_window.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_window.html
@@ -9,18 +9,18 @@ Test window rep
 <head>
   <meta charset="utf-8">
   <title>Rep tests - window</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
 <pre id="test">
-<script src="head.js" type="application/javascript;version=1.8"></script>
-<script type="application/javascript;version=1.8">
+<script src="head.js" type="application/javascript"></script>
+<script type="application/javascript">
 window.onload = Task.async(function* () {
   try {
     let ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
     let React = browserRequire("devtools/client/shared/vendor/react");
 
     const { REPS, MODE } = browserRequire("devtools/client/shared/components/reps/reps");
     let { Rep, Window } = REPS;
 
@@ -72,17 +72,41 @@ window.onload = Task.async(function* () 
 
     const displayClassLongRenderedComponent = renderComponent(Window.rep, {
       object: Object.assign({}, gripStub, {displayClass : "Custom"}),
       mode: MODE.LONG,
       title: "Custom"
     });
     is(displayClassLongRenderedComponent.textContent, "Custom about:newtab",
        "Window rep has expected text content in LONG mode with Custom display class");
-  } catch(e) {
+
+    const objectLinkTinyRenderedComponent = renderComponent(Window.rep, {
+      object: gripStub,
+      mode: MODE.TINY,
+      objectLink: (props, ...children) => React.DOM.span({},
+        "*", ...children, "*"),
+    });
+    is(
+      objectLinkTinyRenderedComponent.textContent,
+      "*Window*",
+      "Window rep has expected text content in TINY mode when an objectLink is passed as a prop"
+    );
+
+    const objectLinkLongRenderedComponent = renderComponent(Window.rep, {
+      object: gripStub,
+      mode: MODE.LONG,
+      objectLink: (props, ...children) => React.DOM.span({},
+        "*", ...children, "*"),
+    });
+    is(
+      objectLinkLongRenderedComponent.textContent,
+      "*Window* about:newtab",
+      "Window rep has expected text content in LONG mode when an objectLink is passed as a prop"
+   );
+  } catch (e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 });
 </script>
 </pre>
 </body>