Bug 1357341 - Reps 0.6.0: update reps bundle from Github; r=jdescottes
authornchevobbe <nchevobbe@mozilla.com>
Thu, 20 Apr 2017 10:43:32 +0200
changeset 354051 76d7e4ef5e0ad82391e7ae56915af0a70ae483c0
parent 354050 e37099ed6eb8a720cb0626eef7cbbc575ac5d247
child 354052 468405fee195426301644c0843303ececb77bece
push id31685
push userkwierso@gmail.com
push dateThu, 20 Apr 2017 21:45:29 +0000
treeherdermozilla-central@5e3dc7e1288a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdescottes
bugs1357341
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1357341 - Reps 0.6.0: update reps bundle from Github; r=jdescottes MozReview-Commit-ID: BW0DPLf8dXf
devtools/client/shared/components/reps/reps.js
devtools/client/shared/components/reps/test/mochitest/head.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
@@ -2,17 +2,17 @@
 	if(typeof exports === 'object' && typeof module === 'object')
 		module.exports = factory(require("devtools/client/shared/vendor/react"));
 	else if(typeof define === 'function' && define.amd)
 		define(["devtools/client/shared/vendor/react"], factory);
 	else {
 		var a = typeof exports === 'object' ? factory(require("devtools/client/shared/vendor/react")) : factory(root["devtools/client/shared/vendor/react"]);
 		for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];
 	}
-})(this, function(__WEBPACK_EXTERNAL_MODULE_3__) {
+})(this, function(__WEBPACK_EXTERNAL_MODULE_4__) {
 return /******/ (function(modules) { // webpackBootstrap
 /******/ 	// The module cache
 /******/ 	var installedModules = {};
 /******/
 /******/ 	// The require function
 /******/ 	function __webpack_require__(moduleId) {
 /******/
 /******/ 		// Check if module is in cache
@@ -50,27 +50,28 @@ return /******/ (function(modules) { // 
 /******/ 	return __webpack_require__(0);
 /******/ })
 /************************************************************************/
 /******/ ([
 /* 0 */
 /***/ function(module, exports, __webpack_require__) {
 
 	const { MODE } = __webpack_require__(1);
-	const { REPS } = __webpack_require__(2);
+	const { REPS, getRep } = __webpack_require__(2);
 	const {
 	  createFactories,
 	  parseURLEncodedText,
 	  parseURLParams,
 	  getSelectableInInspectorGrips,
 	  maybeEscapePropertyName
-	} = __webpack_require__(4);
+	} = __webpack_require__(3);
 	
 	module.exports = {
 	  REPS,
+	  getRep,
 	  MODE,
 	  createFactories,
 	  maybeEscapePropertyName,
 	  parseURLEncodedText,
 	  parseURLParams,
 	  getSelectableInInspectorGrips
 	};
 
@@ -85,20 +86,17 @@ return /******/ (function(modules) { // 
 	    LONG: Symbol("LONG")
 	  }
 	};
 
 /***/ },
 /* 2 */
 /***/ function(module, exports, __webpack_require__) {
 
-	const React = __webpack_require__(3);
-	
-	const { isGrip } = __webpack_require__(4);
-	const { MODE } = __webpack_require__(1);
+	const { isGrip } = __webpack_require__(3);
 	
 	// Load all existing rep templates
 	const Undefined = __webpack_require__(6);
 	const Null = __webpack_require__(7);
 	const StringRep = __webpack_require__(8);
 	const LongStringRep = __webpack_require__(9);
 	const Number = __webpack_require__(10);
 	const ArrayRep = __webpack_require__(11);
@@ -133,31 +131,24 @@ return /******/ (function(modules) { // 
 	let reps = [RegExp, StyleSheet, Event, DateTime, CommentNode, ElementNode, TextNode, Attribute, LongStringRep, Func, PromiseRep, ArrayRep, Document, Window, ObjectWithText, ObjectWithURL, ErrorRep, GripArray, GripMap, Grip, Undefined, Null, StringRep, Number, SymbolRep, InfinityRep, NaNRep];
 	
 	/**
 	 * Generic rep that is using for rendering native JS types or an object.
 	 * The right template used for rendering is picked automatically according
 	 * to the current value type. The value must be passed is as 'object'
 	 * property.
 	 */
-	const Rep = React.createClass({
-	  displayName: "Rep",
-	
-	  propTypes: {
-	    object: React.PropTypes.any,
-	    defaultRep: React.PropTypes.object,
-	    // @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]))
-	  },
-	
-	  render: function () {
-	    let rep = getRep(this.props.object, this.props.defaultRep);
-	    return rep(this.props);
-	  }
-	});
+	const Rep = function (props) {
+	  let {
+	    object,
+	    defaultRep
+	  } = props;
+	  let rep = getRep(object, defaultRep);
+	  return rep(props);
+	};
 	
 	// Helpers
 	
 	/**
 	 * Return a rep object that is responsible for rendering given
 	 * object.
 	 *
 	 * @param object {Object} Object to be rendered in the UI. This
@@ -181,24 +172,24 @@ return /******/ (function(modules) { // 
 	
 	  for (let i = 0; i < reps.length; i++) {
 	    let rep = reps[i];
 	    try {
 	      // supportsObject could return weight (not only true/false
 	      // but a number), which would allow to priorities templates and
 	      // support better extensibility.
 	      if (rep.supportsObject(object, type)) {
-	        return React.createFactory(rep.rep);
+	        return rep.rep;
 	      }
 	    } catch (err) {
 	      console.error(err);
 	    }
 	  }
 	
-	  return React.createFactory(defaultRep.rep);
+	  return defaultRep.rep;
 	}
 	
 	module.exports = {
 	  Rep,
 	  REPS: {
 	    ArrayRep,
 	    Attribute,
 	    CommentNode,
@@ -223,31 +214,27 @@ return /******/ (function(modules) { // 
 	    RegExp,
 	    Rep,
 	    StringRep,
 	    StyleSheet,
 	    SymbolRep,
 	    TextNode,
 	    Undefined,
 	    Window
-	  }
+	  },
+	  // Exporting for tests
+	  getRep
 	};
 
 /***/ },
 /* 3 */
-/***/ function(module, exports) {
-
-	module.exports = __WEBPACK_EXTERNAL_MODULE_3__;
-
-/***/ },
-/* 4 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
-	const React = __webpack_require__(3);
+	const React = __webpack_require__(4);
 	
 	// Utils
 	const nodeConstants = __webpack_require__(5);
 	
 	/**
 	 * Create React factories for given arguments.
 	 * Example:
 	 *   const { Rep } = createFactories(require("./rep"));
@@ -316,22 +303,27 @@ return /******/ (function(modules) { // 
 	/**
 	 * Escape a string so that the result is viewable and valid JS.
 	 * Control characters, other invisibles, invalid characters,
 	 * backslash, and double quotes are escaped.  The resulting string is
 	 * surrounded by double quotes.
 	 *
 	 * @param {String} str
 	 *        the input
+	 * @param {Boolean} escapeWhitespace
+	 *        if true, TAB, CR, and NL characters will be escaped
 	 * @return {String} the escaped string
 	 */
-	function escapeString(str) {
+	function escapeString(str, escapeWhitespace) {
 	  return "\"" + str.replace(escapeRegexp, (match, offset) => {
 	    let c = match.charCodeAt(0);
 	    if (c in escapeMap) {
+	      if (!escapeWhitespace && (c === 9 || c === 0xa || c === 0xd)) {
+	        return match[0];
+	      }
 	      return escapeMap[c];
 	    }
 	    if (c >= 0xd800 && c <= 0xdfff) {
 	      // Find the full code point containing the surrogate, with a
 	      // special case for a trailing surrogate at the start of the
 	      // string.
 	      if (c >= 0xdc00 && offset > 0) {
 	        --offset;
@@ -490,28 +482,31 @@ return /******/ (function(modules) { // 
 	  };
 	}
 	
 	/**
 	 * Wrap the provided render() method of a rep in a try/catch block that will render a
 	 * fallback rep if the render fails.
 	 */
 	function wrapRender(renderMethod) {
-	  return function () {
+	  const wrappedFunction = function (props) {
 	    try {
-	      return renderMethod.call(this);
+	      return renderMethod.call(this, props);
 	    } catch (e) {
+	      console.error(e);
 	      return React.DOM.span({
 	        className: "objectBox objectBox-failure",
 	        title: "This object could not be rendered, " + "please file a bug on bugzilla.mozilla.org"
 	      },
 	      /* Labels have to be hardcoded for reps, see Bug 1317038. */
 	      "Invalid object");
 	    }
 	  };
+	  wrappedFunction.propTypes = renderMethod.propTypes;
+	  return wrappedFunction;
 	}
 	
 	/**
 	 * Get an array of all the items from the grip in parameter (including the grip itself)
 	 * which can be selected in the inspector.
 	 *
 	 * @param {Object} Grip
 	 * @return {Array} Flat array of the grips which can be selected in the inspector
@@ -594,34 +589,71 @@ return /******/ (function(modules) { // 
 	    }
 	
 	    return propertiesValues;
 	  }
 	
 	  return [];
 	}
 	
+	/**
+	 * Returns a new element wrapped with a component, props.objectLink if it exists,
+	 * or a span if there are multiple childs, or directly the child if only one is passed.
+	 *
+	 * @param {Object} props A Rep "props" object that may contain `objectLink`
+	 *                 and `object` properties.
+	 * @param {Object} config Object to pass as props to the `objectLink` component.
+	 * @param {...Element} children Elements to be wrapped with the `objectLink` component.
+	 * @return {Element} Element, wrapped or not, depending if `objectLink`
+	 *                   was supplied in props.
+	 */
+	function safeObjectLink(props, config, ...children) {
+	  const {
+	    objectLink,
+	    object
+	  } = props;
+	
+	  if (objectLink) {
+	    return objectLink(Object.assign({
+	      object
+	    }, config), ...children);
+	  }
+	
+	  if ((!config || Object.keys(config).length === 0) && children.length === 1) {
+	    return children[0];
+	  }
+	
+	  return React.DOM.span(config, ...children);
+	}
+	
 	module.exports = {
 	  createFactories,
 	  isGrip,
 	  cropString,
 	  rawCropString,
 	  sanitizeString,
 	  escapeString,
 	  wrapRender,
 	  cropMultipleLines,
 	  parseURLParams,
 	  parseURLEncodedText,
 	  getFileName,
 	  getURLDisplayString,
 	  getSelectableInInspectorGrips,
-	  maybeEscapePropertyName
+	  maybeEscapePropertyName,
+	  safeObjectLink
 	};
 
 /***/ },
+/* 4 */
+/***/ function(module, exports) {
+
+	module.exports = __WEBPACK_EXTERNAL_MODULE_4__;
+
+/***/ },
 /* 5 */
 /***/ function(module, exports) {
 
 	module.exports = {
 	  ELEMENT_NODE: 1,
 	  ATTRIBUTE_NODE: 2,
 	  TEXT_NODE: 3,
 	  CDATA_SECTION_NODE: 4,
@@ -643,1932 +675,1692 @@ return /******/ (function(modules) { // 
 	  DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC: 0x20
 	};
 
 /***/ },
 /* 6 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
-	const React = __webpack_require__(3);
-	
-	const { wrapRender } = __webpack_require__(4);
+	const React = __webpack_require__(4);
+	
+	const { wrapRender } = __webpack_require__(3);
 	
 	// Shortcuts
 	const { span } = React.DOM;
 	
 	/**
 	 * Renders undefined value
 	 */
-	const Undefined = React.createClass({
-	  displayName: "UndefinedRep",
-	
-	  render: wrapRender(function () {
-	    return span({ className: "objectBox objectBox-undefined" }, "undefined");
-	  })
-	});
+	const Undefined = function () {
+	  return span({ className: "objectBox objectBox-undefined" }, "undefined");
+	};
 	
 	function supportsObject(object, type) {
 	  if (object && object.type && object.type == "undefined") {
 	    return true;
 	  }
 	
 	  return type == "undefined";
 	}
 	
 	// Exports from this module
 	
 	module.exports = {
-	  rep: Undefined,
-	  supportsObject: supportsObject
+	  rep: wrapRender(Undefined),
+	  supportsObject
 	};
 
 /***/ },
 /* 7 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
-	const React = __webpack_require__(3);
-	
-	const { wrapRender } = __webpack_require__(4);
+	const React = __webpack_require__(4);
+	
+	const { wrapRender } = __webpack_require__(3);
 	
 	// Shortcuts
 	const { span } = React.DOM;
 	
 	/**
 	 * Renders null value
 	 */
-	const Null = React.createClass({
-	  displayName: "NullRep",
-	
-	  render: wrapRender(function () {
-	    return span({ className: "objectBox objectBox-null" }, "null");
-	  })
-	});
+	function Null(props) {
+	  return span({ className: "objectBox objectBox-null" }, "null");
+	}
 	
 	function supportsObject(object, type) {
 	  if (object && object.type && object.type == "null") {
 	    return true;
 	  }
 	
 	  return object == null;
 	}
 	
 	// Exports from this module
 	
 	module.exports = {
-	  rep: Null,
-	  supportsObject: supportsObject
+	  rep: wrapRender(Null),
+	  supportsObject
 	};
 
 /***/ },
 /* 8 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
-	const React = __webpack_require__(3);
+	const React = __webpack_require__(4);
 	
 	const {
 	  escapeString,
 	  rawCropString,
 	  sanitizeString,
 	  wrapRender
-	} = __webpack_require__(4);
+	} = __webpack_require__(3);
 	
 	// Shortcuts
 	const { span } = React.DOM;
 	
 	/**
 	 * Renders a string. String value is enclosed within quotes.
 	 */
-	const StringRep = React.createClass({
-	  displayName: "StringRep",
-	
-	  propTypes: {
-	    useQuotes: React.PropTypes.bool,
-	    style: React.PropTypes.object,
-	    object: React.PropTypes.string.isRequired,
-	    member: React.PropTypes.any,
-	    cropLimit: React.PropTypes.number
-	  },
-	
-	  getDefaultProps: function () {
-	    return {
-	      useQuotes: true
-	    };
-	  },
-	
-	  render: wrapRender(function () {
-	    let text = this.props.object;
-	    let member = this.props.member;
-	    let style = this.props.style;
-	
-	    let config = { className: "objectBox objectBox-string" };
-	    if (style) {
-	      config.style = style;
-	    }
-	
-	    if (this.props.useQuotes) {
-	      text = escapeString(text);
-	    } else {
-	      text = sanitizeString(text);
-	    }
-	
-	    if ((!member || !member.open) && this.props.cropLimit) {
-	      text = rawCropString(text, this.props.cropLimit);
-	    }
-	
-	    return span(config, text);
-	  })
-	});
+	StringRep.propTypes = {
+	  useQuotes: React.PropTypes.bool,
+	  escapeWhitespace: React.PropTypes.bool,
+	  style: React.PropTypes.object,
+	  object: React.PropTypes.string.isRequired,
+	  member: React.PropTypes.any,
+	  cropLimit: React.PropTypes.number
+	};
+	
+	function StringRep(props) {
+	  let {
+	    cropLimit,
+	    object: text,
+	    member,
+	    style,
+	    useQuotes = true,
+	    escapeWhitespace = true
+	  } = props;
+	
+	  let config = { className: "objectBox objectBox-string" };
+	  if (style) {
+	    config.style = style;
+	  }
+	
+	  if (useQuotes) {
+	    text = escapeString(text, escapeWhitespace);
+	  } else {
+	    text = sanitizeString(text);
+	  }
+	
+	  if ((!member || !member.open) && cropLimit) {
+	    text = rawCropString(text, cropLimit);
+	  }
+	
+	  return span(config, text);
+	}
 	
 	function supportsObject(object, type) {
 	  return type == "string";
 	}
 	
 	// Exports from this module
 	
 	module.exports = {
-	  rep: StringRep,
-	  supportsObject: supportsObject
+	  rep: wrapRender(StringRep),
+	  supportsObject
 	};
 
 /***/ },
 /* 9 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
-	const React = __webpack_require__(3);
+	const React = __webpack_require__(4);
 	const {
 	  escapeString,
 	  sanitizeString,
 	  isGrip,
 	  wrapRender
-	} = __webpack_require__(4);
+	} = __webpack_require__(3);
 	// Shortcuts
 	const { span } = React.DOM;
 	
 	/**
 	 * Renders a long string grip.
 	 */
-	const LongStringRep = React.createClass({
-	  displayName: "LongStringRep",
-	
-	  propTypes: {
-	    useQuotes: React.PropTypes.bool,
-	    style: React.PropTypes.object,
-	    cropLimit: React.PropTypes.number.isRequired,
-	    member: React.PropTypes.string,
-	    object: React.PropTypes.object.isRequired
-	  },
-	
-	  getDefaultProps: function () {
-	    return {
-	      useQuotes: true
-	    };
-	  },
-	
-	  render: wrapRender(function () {
-	    let {
-	      cropLimit,
-	      member,
-	      object,
-	      style,
-	      useQuotes
-	    } = this.props;
-	    let { fullText, initial, length } = object;
-	
-	    let config = { className: "objectBox objectBox-string" };
-	    if (style) {
-	      config.style = style;
-	    }
-	
-	    let string = member && member.open ? fullText || initial : initial.substring(0, cropLimit);
-	
-	    if (string.length < length) {
-	      string += "\u2026";
-	    }
-	    let formattedString = useQuotes ? escapeString(string) : sanitizeString(string);
-	    return span(config, formattedString);
-	  })
-	});
+	LongStringRep.propTypes = {
+	  useQuotes: React.PropTypes.bool,
+	  escapeWhitespace: React.PropTypes.bool,
+	  style: React.PropTypes.object,
+	  cropLimit: React.PropTypes.number.isRequired,
+	  member: React.PropTypes.string,
+	  object: React.PropTypes.object.isRequired
+	};
+	
+	function LongStringRep(props) {
+	  let {
+	    cropLimit,
+	    member,
+	    object,
+	    style,
+	    useQuotes = true,
+	    escapeWhitespace = true
+	  } = props;
+	  let { fullText, initial, length } = object;
+	
+	  let config = { className: "objectBox objectBox-string" };
+	  if (style) {
+	    config.style = style;
+	  }
+	
+	  let string = member && member.open ? fullText || initial : initial.substring(0, cropLimit);
+	
+	  if (string.length < length) {
+	    string += "\u2026";
+	  }
+	  let formattedString = useQuotes ? escapeString(string, escapeWhitespace) : sanitizeString(string);
+	  return span(config, formattedString);
+	}
 	
 	function supportsObject(object, type) {
 	  if (!isGrip(object)) {
 	    return false;
 	  }
 	  return object.type === "longString";
 	}
 	
 	// Exports from this module
 	module.exports = {
-	  rep: LongStringRep,
-	  supportsObject: supportsObject
+	  rep: wrapRender(LongStringRep),
+	  supportsObject
 	};
 
 /***/ },
 /* 10 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
-	const React = __webpack_require__(3);
-	
-	const { wrapRender } = __webpack_require__(4);
+	const React = __webpack_require__(4);
+	
+	const { wrapRender } = __webpack_require__(3);
 	
 	// Shortcuts
 	const { span } = React.DOM;
 	
 	/**
 	 * Renders a number
 	 */
-	const Number = React.createClass({
-	  displayName: "Number",
-	
-	  propTypes: {
-	    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);
-	  },
-	
-	  render: wrapRender(function () {
-	    let value = this.props.object;
-	
-	    return span({ className: "objectBox objectBox-number" }, this.stringify(value));
-	  })
-	});
+	Number.propTypes = {
+	  object: React.PropTypes.oneOfType([React.PropTypes.object, React.PropTypes.number, React.PropTypes.bool]).isRequired
+	};
+	
+	function Number(props) {
+	  let value = props.object;
+	
+	  return span({ className: "objectBox objectBox-number" }, stringify(value));
+	}
+	
+	function stringify(object) {
+	  let isNegativeZero = Object.is(object, -0) || object.type && object.type == "-0";
+	
+	  return isNegativeZero ? "-0" : String(object);
+	}
 	
 	function supportsObject(object, type) {
 	  return ["boolean", "number", "-0"].includes(type);
 	}
 	
 	// Exports from this module
 	
 	module.exports = {
-	  rep: Number,
-	  supportsObject: supportsObject
+	  rep: wrapRender(Number),
+	  supportsObject
 	};
 
 /***/ },
 /* 11 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
-	const React = __webpack_require__(3);
+	const React = __webpack_require__(4);
 	const {
-	  createFactories,
+	  safeObjectLink,
 	  wrapRender
-	} = __webpack_require__(4);
-	const Caption = React.createFactory(__webpack_require__(12));
+	} = __webpack_require__(3);
+	const Caption = __webpack_require__(12);
 	const { MODE } = __webpack_require__(1);
 	
 	const ModePropType = React.PropTypes.oneOf(
 	// @TODO Change this to Object.values once it's supported in Node's version of V8
 	Object.keys(MODE).map(key => MODE[key]));
 	
 	// Shortcuts
 	const DOM = 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 ArrayRep = React.createClass({
-	  displayName: "ArrayRep",
-	
-	  propTypes: {
-	    mode: ModePropType,
-	    objectLink: React.PropTypes.func,
-	    object: React.PropTypes.array.isRequired
-	  },
-	
-	  getTitle: function (object, context) {
-	    return "[" + object.length + "]";
-	  },
-	
-	  arrayIterator: function (array, max) {
-	    let items = [];
-	    let delim;
-	
-	    for (let i = 0; i < array.length && i < max; i++) {
-	      try {
-	        let value = array[i];
-	
-	        delim = i == array.length - 1 ? "" : ", ";
-	
-	        items.push(ItemRep({
-	          object: value,
-	          // Hardcode tiny mode to avoid recursive handling.
-	          mode: MODE.TINY,
-	          delim: delim
-	        }));
-	      } catch (exc) {
-	        items.push(ItemRep({
-	          object: exc,
-	          mode: MODE.TINY,
-	          delim: delim
-	        }));
-	      }
-	    }
-	
-	    if (array.length > max) {
-	      items.push(Caption({
-	        object: this.safeObjectLink({
-	          object: this.props.object
-	        }, array.length - max + " more…")
+	ArrayRep.propTypes = {
+	  mode: ModePropType,
+	  objectLink: React.PropTypes.func,
+	  object: React.PropTypes.array.isRequired
+	};
+	
+	function ArrayRep(props) {
+	  let {
+	    object,
+	    mode = MODE.SHORT
+	  } = props;
+	
+	  let items;
+	  let brackets;
+	  let needSpace = function (space) {
+	    return space ? { left: "[ ", right: " ]" } : { left: "[", right: "]" };
+	  };
+	
+	  if (mode === MODE.TINY) {
+	    let isEmpty = object.length === 0;
+	    items = [DOM.span({ className: "length" }, isEmpty ? "" : object.length)];
+	    brackets = needSpace(false);
+	  } else {
+	    let max = mode === MODE.SHORT ? 3 : 10;
+	    items = arrayIterator(props, object, max);
+	    brackets = needSpace(items.length > 0);
+	  }
+	
+	  return DOM.span({
+	    className: "objectBox objectBox-array" }, safeObjectLink(props, {
+	    className: "arrayLeftBracket",
+	    object: object
+	  }, brackets.left), ...items, safeObjectLink(props, {
+	    className: "arrayRightBracket",
+	    object: object
+	  }, brackets.right), DOM.span({
+	    className: "arrayProperties",
+	    role: "group" }));
+	}
+	
+	function arrayIterator(props, array, max) {
+	  let items = [];
+	  let delim;
+	
+	  for (let i = 0; i < array.length && i < max; i++) {
+	    try {
+	      let value = array[i];
+	
+	      delim = i == array.length - 1 ? "" : ", ";
+	
+	      items.push(ItemRep({
+	        object: value,
+	        // Hardcode tiny mode to avoid recursive handling.
+	        mode: MODE.TINY,
+	        delim: delim
+	      }));
+	    } catch (exc) {
+	      items.push(ItemRep({
+	        object: exc,
+	        mode: MODE.TINY,
+	        delim: delim
 	      }));
 	    }
-	
-	    return items;
-	  },
-	
-	  /**
-	   * Returns true if the passed object is an array with additional (custom)
-	   * properties, otherwise returns false. Custom properties should be
-	   * displayed in extra expandable section.
-	   *
-	   * Example array with a custom property.
-	   * let arr = [0, 1];
-	   * arr.myProp = "Hello";
-	   *
-	   * @param {Array} array The array object.
-	   */
-	  hasSpecialProperties: function (array) {
-	    function isInteger(x) {
-	      let y = parseInt(x, 10);
-	      if (isNaN(y)) {
-	        return false;
-	      }
-	      return x === y.toString();
-	    }
-	
-	    let propsArray = Object.getOwnPropertyNames(array);
-	    for (let i = 0; i < propsArray.length; i++) {
-	      let p = propsArray[i];
-	
-	      // Valid indexes are skipped
-	      if (isInteger(p)) {
-	        continue;
-	      }
-	
-	      // Ignore standard 'length' property, anything else is custom.
-	      if (p != "length") {
-	        return true;
-	      }
-	    }
-	
-	    return false;
-	  },
-	
-	  // 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;
-	    let needSpace = function (space) {
-	      return space ? { left: "[ ", right: " ]" } : { left: "[", right: "]" };
-	    };
-	
-	    if (mode === MODE.TINY) {
-	      let isEmpty = object.length === 0;
-	      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);
-	    }
-	
-	    return DOM.span({
-	      className: "objectBox objectBox-array" }, this.safeObjectLink({
-	      className: "arrayLeftBracket",
-	      object: object
-	    }, brackets.left), ...items, this.safeObjectLink({
-	      className: "arrayRightBracket",
-	      object: object
-	    }, brackets.right), DOM.span({
-	      className: "arrayProperties",
-	      role: "group" }));
-	  })
-	});
+	  }
+	
+	  if (array.length > max) {
+	    items.push(Caption({
+	      object: safeObjectLink(props, {
+	        object: props.object
+	      }, array.length - max + " more…")
+	    }));
+	  }
+	
+	  return items;
+	}
 	
 	/**
 	 * Renders array item. Individual values are separated by a comma.
 	 */
-	let ItemRep = React.createFactory(React.createClass({
-	  displayName: "ItemRep",
-	
-	  propTypes: {
-	    object: React.PropTypes.any.isRequired,
-	    delim: React.PropTypes.string.isRequired,
-	    mode: ModePropType
-	  },
-	
-	  render: wrapRender(function () {
-	    const { Rep } = createFactories(__webpack_require__(2));
-	
-	    let object = this.props.object;
-	    let delim = this.props.delim;
-	    let mode = this.props.mode;
-	    return DOM.span({}, Rep({ object: object, mode: mode }), delim);
-	  })
-	}));
+	ItemRep.propTypes = {
+	  object: React.PropTypes.any.isRequired,
+	  delim: React.PropTypes.string.isRequired,
+	  mode: ModePropType
+	};
+	
+	function ItemRep(props) {
+	  const { Rep } = __webpack_require__(2);
+	
+	  let {
+	    object,
+	    delim,
+	    mode
+	  } = props;
+	  return DOM.span({}, Rep({ object: object, mode: mode }), delim);
+	}
 	
 	function supportsObject(object, type) {
 	  return Array.isArray(object) || Object.prototype.toString.call(object) === "[object Arguments]";
 	}
 	
 	// Exports from this module
 	module.exports = {
-	  rep: ArrayRep,
-	  supportsObject: supportsObject
+	  rep: wrapRender(ArrayRep),
+	  supportsObject
 	};
 
 /***/ },
 /* 12 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
-	const React = __webpack_require__(3);
+	const React = __webpack_require__(4);
 	const DOM = React.DOM;
 	
-	const { wrapRender } = __webpack_require__(4);
+	const { wrapRender } = __webpack_require__(3);
 	
 	/**
 	 * 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.oneOfType([React.PropTypes.number, React.PropTypes.string]).isRequired
-	  },
-	
-	  render: wrapRender(function () {
-	    return DOM.span({ "className": "caption" }, this.props.object);
-	  })
-	});
+	Caption.propTypes = {
+	  object: React.PropTypes.oneOfType([React.PropTypes.number, React.PropTypes.string]).isRequired
+	};
+	
+	function Caption(props) {
+	  return DOM.span({ "className": "caption" }, props.object);
+	}
 	
 	// Exports from this module
-	module.exports = Caption;
+	module.exports = wrapRender(Caption);
 
 /***/ },
 /* 13 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
-	const React = __webpack_require__(3);
+	const React = __webpack_require__(4);
 	const {
+	  safeObjectLink,
 	  wrapRender
-	} = __webpack_require__(4);
-	const Caption = React.createFactory(__webpack_require__(12));
-	const PropRep = React.createFactory(__webpack_require__(14));
+	} = __webpack_require__(3);
+	const Caption = __webpack_require__(12);
+	const PropRep = __webpack_require__(14);
 	const { MODE } = __webpack_require__(1);
 	// Shortcuts
 	const { span } = React.DOM;
 	/**
 	 * Renders an object. An object is represented by a list of its
 	 * properties enclosed in curly brackets.
 	 */
-	const Obj = React.createClass({
-	  displayName: "Obj",
-	
-	  propTypes: {
-	    object: React.PropTypes.object.isRequired,
-	    // @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";
-	    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);
-	    }
-	    return [];
-	  },
-	
-	  propIterator: function (object, max) {
-	    let isInterestingProp = (t, value) => {
-	      // Do not pick objects, it could cause recursion.
-	      return t == "boolean" || t == "number" || t == "string" && value;
-	    };
-	
-	    // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=945377
-	    if (Object.prototype.toString.call(object) === "[object Generator]") {
-	      object = Object.getPrototypeOf(object);
-	    }
-	
-	    // Object members with non-empty values are preferred since it gives the
-	    // user a better overview of the object.
-	    let propsArray = this.getPropsArray(object, max, isInterestingProp);
-	
-	    if (propsArray.length <= max) {
-	      // There are not enough props yet (or at least, not enough props to
-	      // be able to know whether we should print "more…" or not).
-	      // Let's display also empty members and functions.
-	      propsArray = propsArray.concat(this.getPropsArray(object, max, (t, value) => {
-	        return !isInterestingProp(t, value);
-	      }));
-	    }
-	
-	    if (propsArray.length > max) {
-	      propsArray.pop();
-	      let objectLink = this.props.objectLink || span;
-	
-	      propsArray.push(Caption({
-	        object: objectLink({
-	          object: object
-	        }, Object.keys(object).length - max + " more…")
-	      }));
-	    } else if (propsArray.length > 0) {
-	      // Remove the last comma.
-	      propsArray[propsArray.length - 1] = React.cloneElement(propsArray[propsArray.length - 1], { delim: "" });
-	    }
-	
+	ObjectRep.propTypes = {
+	  object: React.PropTypes.object.isRequired,
+	  // @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
+	};
+	
+	function ObjectRep(props) {
+	  let object = props.object;
+	  let propsArray = safePropIterator(props, object);
+	
+	  if (props.mode === MODE.TINY || !propsArray.length) {
+	    return span({ className: "objectBox objectBox-object" }, getTitle(props, object));
+	  }
+	
+	  return span({ className: "objectBox objectBox-object" }, getTitle(props, object), safeObjectLink(props, {
+	    className: "objectLeftBrace"
+	  }, " { "), ...propsArray, safeObjectLink(props, {
+	    className: "objectRightBrace"
+	  }, " }"));
+	}
+	
+	function getTitle(props, object) {
+	  let title = props.title || object.class || "Object";
+	  return safeObjectLink(props, { className: "objectTitle" }, title);
+	}
+	
+	function safePropIterator(props, object, max) {
+	  max = typeof max === "undefined" ? 3 : max;
+	  try {
+	    return propIterator(props, object, max);
+	  } catch (err) {
+	    console.error(err);
+	  }
+	  return [];
+	}
+	
+	function propIterator(props, object, max) {
+	  let isInterestingProp = (type, value) => {
+	    // Do not pick objects, it could cause recursion.
+	    return type == "boolean" || type == "number" || type == "string" && value;
+	  };
+	
+	  // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=945377
+	  if (Object.prototype.toString.call(object) === "[object Generator]") {
+	    object = Object.getPrototypeOf(object);
+	  }
+	
+	  // Object members with non-empty values are preferred since it gives the
+	  // user a better overview of the object.
+	  let interestingObject = getFilteredObject(object, max, isInterestingProp);
+	
+	  if (Object.keys(interestingObject).length < max) {
+	    // There are not enough props yet (or at least, not enough props to
+	    // be able to know whether we should print "more…" or not).
+	    // Let's display also empty members and functions.
+	    interestingObject = Object.assign({}, interestingObject, getFilteredObject(object, max - Object.keys(interestingObject).length, (type, value) => !isInterestingProp(type, value)));
+	  }
+	
+	  const truncated = Object.keys(object).length > max;
+	  let propsArray = getPropsArray(interestingObject, truncated);
+	  if (truncated) {
+	    propsArray.push(Caption({
+	      object: safeObjectLink(props, {}, Object.keys(object).length - max + " more…")
+	    }));
+	  }
+	
+	  return propsArray;
+	}
+	
+	/**
+	 * Get an array of components representing the properties of the object
+	 *
+	 * @param {Object} object
+	 * @param {Boolean} truncated true if the object is truncated.
+	 * @return {Array} Array of PropRep.
+	 */
+	function getPropsArray(object, truncated) {
+	  let propsArray = [];
+	
+	  if (!object) {
 	    return propsArray;
-	  },
-	
-	  getPropsArray: function (object, max, filter) {
-	    let propsArray = [];
-	
-	    max = max || 3;
-	    if (!object) {
-	      return propsArray;
-	    }
-	
-	    // Hardcode tiny mode to avoid recursive handling.
-	    let mode = MODE.TINY;
-	
-	    try {
-	      for (let name in object) {
-	        if (propsArray.length > max) {
-	          return propsArray;
-	        }
-	
-	        let value;
-	        try {
-	          value = object[name];
-	        } catch (exc) {
-	          continue;
-	        }
-	
-	        let t = typeof value;
-	        if (filter(t, value)) {
-	          propsArray.push(PropRep({
-	            mode: mode,
-	            name: name,
-	            object: value,
-	            equal: ": ",
-	            delim: ", "
-	          }));
-	        }
+	  }
+	
+	  // Hardcode tiny mode to avoid recursive handling.
+	  let mode = MODE.TINY;
+	  const objectKeys = Object.keys(object);
+	  return objectKeys.map((name, i) => PropRep({
+	    mode,
+	    name,
+	    object: object[name],
+	    equal: ": ",
+	    delim: i !== objectKeys.length - 1 || truncated ? ", " : null
+	  }));
+	}
+	
+	/**
+	 * Get a copy of the object filtered by a given predicate.
+	 *
+	 * @param {Object} object.
+	 * @param {Number} max The maximum length of keys array.
+	 * @param {Function} filter Filter the props you want.
+	 * @return {Object} the filtered object.
+	 */
+	function getFilteredObject(object, max, filter) {
+	  let filteredObject = {};
+	
+	  try {
+	    for (let name in object) {
+	      if (Object.keys(filteredObject).length >= max) {
+	        return filteredObject;
 	      }
-	    } 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];
+	
+	      let value;
+	      try {
+	        value = object[name];
+	      } catch (exc) {
+	        continue;
+	      }
+	
+	      let t = typeof value;
+	      if (filter(t, value)) {
+	        filteredObject[name] = value;
+	      }
 	    }
-	
-	    return span(config, ...children);
-	  },
-	
-	  render: wrapRender(function () {
-	    let object = this.props.object;
-	    let propsArray = this.safePropIterator(object);
-	
-	    if (this.props.mode === MODE.TINY || !propsArray.length) {
-	      return span({ className: "objectBox objectBox-object" }, this.getTitle(object));
-	    }
-	
-	    return span({ className: "objectBox objectBox-object" }, this.getTitle(object), this.safeObjectLink({
-	      className: "objectLeftBrace"
-	    }, " { "), ...propsArray, this.safeObjectLink({
-	      className: "objectRightBrace"
-	    }, " }"));
-	  })
-	});
+	  } catch (err) {
+	    console.error(err);
+	  }
+	  return filteredObject;
+	}
+	
 	function supportsObject(object, type) {
 	  return true;
 	}
 	
 	// Exports from this module
 	module.exports = {
-	  rep: Obj,
-	  supportsObject: supportsObject
+	  rep: wrapRender(ObjectRep),
+	  supportsObject
 	};
 
 /***/ },
 /* 14 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
-	const React = __webpack_require__(3);
+	const React = __webpack_require__(4);
 	const {
-	  createFactories,
 	  maybeEscapePropertyName,
 	  wrapRender
-	} = __webpack_require__(4);
+	} = __webpack_require__(3);
 	const { MODE } = __webpack_require__(1);
 	// Shortcuts
 	const { span } = React.DOM;
 	
 	/**
 	 * Property for Obj (local JS objects), Grip (remote JS objects)
 	 * and GripMap (remote JS maps and weakmaps) reps.
 	 * It's used to render object properties.
 	 */
-	let PropRep = React.createClass({
-	  displayName: "PropRep",
-	
-	  propTypes: {
-	    // Property name.
-	    name: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.object]).isRequired,
-	    // Equal character rendered between property name and value.
-	    equal: React.PropTypes.string,
-	    // Delimiter character used to separate individual properties.
-	    delim: React.PropTypes.string,
-	    // @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,
-	    attachedActorIds: React.PropTypes.array,
-	    onDOMNodeMouseOver: React.PropTypes.func,
-	    onDOMNodeMouseOut: React.PropTypes.func,
-	    onInspectIconClick: React.PropTypes.func,
-	    // Normally a PropRep will quote a property name that isn't valid
-	    // when unquoted; but this flag can be used to suppress the
-	    // quoting.
-	    suppressQuotes: React.PropTypes.bool
-	  },
-	
-	  render: wrapRender(function () {
-	    const Grip = __webpack_require__(15);
-	    let { Rep } = createFactories(__webpack_require__(2));
-	    let {
-	      name,
-	      mode,
-	      equal,
-	      delim,
-	      suppressQuotes
-	    } = this.props;
-	
-	    let key;
-	    // The key can be a simple string, for plain objects,
-	    // or another object for maps and weakmaps.
-	    if (typeof name === "string") {
-	      if (!suppressQuotes) {
-	        name = maybeEscapePropertyName(name);
-	      }
-	      key = span({ "className": "nodeName" }, name);
-	    } else {
-	      key = Rep(Object.assign({}, this.props, {
-	        object: name,
-	        mode: mode || MODE.TINY,
-	        defaultRep: Grip
-	      }));
+	PropRep.propTypes = {
+	  // Property name.
+	  name: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.object]).isRequired,
+	  // Equal character rendered between property name and value.
+	  equal: React.PropTypes.string,
+	  // Delimiter character used to separate individual properties.
+	  delim: React.PropTypes.string,
+	  // @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,
+	  onDOMNodeMouseOver: React.PropTypes.func,
+	  onDOMNodeMouseOut: React.PropTypes.func,
+	  onInspectIconClick: React.PropTypes.func,
+	  // Normally a PropRep will quote a property name that isn't valid
+	  // when unquoted; but this flag can be used to suppress the
+	  // quoting.
+	  suppressQuotes: React.PropTypes.bool
+	};
+	
+	function PropRep(props) {
+	  const Grip = __webpack_require__(15);
+	  const { Rep } = __webpack_require__(2);
+	
+	  let {
+	    name,
+	    mode,
+	    equal,
+	    delim,
+	    suppressQuotes
+	  } = props;
+	
+	  let key;
+	  // The key can be a simple string, for plain objects,
+	  // or another object for maps and weakmaps.
+	  if (typeof name === "string") {
+	    if (!suppressQuotes) {
+	      name = maybeEscapePropertyName(name);
 	    }
-	
-	    return span({}, key, span({
-	      "className": "objectEqual"
-	    }, equal), Rep(Object.assign({}, this.props)), span({
+	    key = span({ "className": "nodeName" }, name);
+	  } else {
+	    key = Rep(Object.assign({}, props, {
+	      object: name,
+	      mode: mode || MODE.TINY,
+	      defaultRep: Grip
+	    }));
+	  }
+	
+	  let delimElement;
+	  if (delim) {
+	    delimElement = span({
 	      "className": "objectComma"
-	    }, delim));
-	  })
-	});
+	    }, delim);
+	  }
+	
+	  return span({}, key, span({
+	    "className": "objectEqual"
+	  }, equal), Rep(Object.assign({}, props)), delimElement);
+	}
 	
 	// Exports from this module
-	module.exports = PropRep;
+	module.exports = wrapRender(PropRep);
 
 /***/ },
 /* 15 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// ReactJS
-	const React = __webpack_require__(3);
+	const React = __webpack_require__(4);
 	// Dependencies
 	const {
-	  createFactories,
 	  isGrip,
+	  safeObjectLink,
 	  wrapRender
-	} = __webpack_require__(4);
-	const Caption = React.createFactory(__webpack_require__(12));
-	const PropRep = React.createFactory(__webpack_require__(14));
+	} = __webpack_require__(3);
+	const Caption = __webpack_require__(12);
+	const PropRep = __webpack_require__(14);
 	const { MODE } = __webpack_require__(1);
 	// Shortcuts
 	const { span } = React.DOM;
 	
 	/**
 	 * Renders generic grip. Grip is client representation
 	 * of remote JS object and is used as an input object
 	 * for this rep component.
 	 */
-	const GripRep = React.createClass({
-	  displayName: "Grip",
-	
-	  propTypes: {
-	    object: React.PropTypes.object.isRequired,
-	    // @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])),
-	    isInterestingProp: React.PropTypes.func,
-	    title: React.PropTypes.string,
-	    objectLink: React.PropTypes.func,
-	    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";
-	    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);
-	    }
-	    return [];
-	  },
-	
-	  propIterator: function (object, max) {
-	    if (object.preview && Object.keys(object.preview).includes("wrappedValue")) {
-	      const { Rep } = createFactories(__webpack_require__(2));
-	
-	      return [Rep({
-	        object: object.preview.wrappedValue,
-	        mode: this.props.mode || MODE.TINY,
-	        defaultRep: Grip
-	      })];
-	    }
-	
-	    // Property filter. Show only interesting properties to the user.
-	    let isInterestingProp = this.props.isInterestingProp || ((type, value) => {
-	      return type == "boolean" || type == "number" || type == "string" && value.length != 0;
-	    });
-	
-	    let properties = object.preview ? object.preview.ownProperties : {};
-	    let propertiesLength = object.preview && object.preview.ownPropertiesLength ? object.preview.ownPropertiesLength : object.ownPropertyLength;
-	
-	    if (object.preview && object.preview.safeGetterValues) {
-	      properties = Object.assign({}, properties, object.preview.safeGetterValues);
-	      propertiesLength += Object.keys(object.preview.safeGetterValues).length;
-	    }
-	
-	    let indexes = this.getPropIndexes(properties, max, isInterestingProp);
-	    if (indexes.length < max && indexes.length < propertiesLength) {
-	      // There are not enough props yet. Then add uninteresting props to display them.
-	      indexes = indexes.concat(this.getPropIndexes(properties, max - indexes.length, (t, value, name) => {
-	        return !isInterestingProp(t, value, name);
-	      }));
-	    }
-	
-	    const truncate = Object.keys(properties).length > max;
-	    // 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...".
-	      propsArray.push(Caption({
-	        object: this.safeObjectLink({}, `${propertiesLength - max} more…`)
-	      }));
+	GripRep.propTypes = {
+	  object: React.PropTypes.object.isRequired,
+	  // @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])),
+	  isInterestingProp: React.PropTypes.func,
+	  title: React.PropTypes.string,
+	  objectLink: React.PropTypes.func,
+	  onDOMNodeMouseOver: React.PropTypes.func,
+	  onDOMNodeMouseOut: React.PropTypes.func,
+	  onInspectIconClick: React.PropTypes.func
+	};
+	
+	function GripRep(props) {
+	  let object = props.object;
+	  let propsArray = safePropIterator(props, object, props.mode === MODE.LONG ? 10 : 3);
+	
+	  if (props.mode === MODE.TINY) {
+	    return span({ className: "objectBox objectBox-object" }, getTitle(props, object));
+	  }
+	
+	  return span({ className: "objectBox objectBox-object" }, getTitle(props, object), safeObjectLink(props, {
+	    className: "objectLeftBrace"
+	  }, " { "), ...propsArray, safeObjectLink(props, {
+	    className: "objectRightBrace"
+	  }, " }"));
+	}
+	
+	function getTitle(props, object) {
+	  let title = props.title || object.class || "Object";
+	  return safeObjectLink(props, {}, title);
+	}
+	
+	function safePropIterator(props, object, max) {
+	  max = typeof max === "undefined" ? 3 : max;
+	  try {
+	    return propIterator(props, object, max);
+	  } catch (err) {
+	    console.error(err);
+	  }
+	  return [];
+	}
+	
+	function propIterator(props, object, max) {
+	  if (object.preview && Object.keys(object.preview).includes("wrappedValue")) {
+	    const { Rep } = __webpack_require__(2);
+	
+	    return [Rep({
+	      object: object.preview.wrappedValue,
+	      mode: props.mode || MODE.TINY,
+	      defaultRep: Grip
+	    })];
+	  }
+	
+	  // Property filter. Show only interesting properties to the user.
+	  let isInterestingProp = props.isInterestingProp || ((type, value) => {
+	    return type == "boolean" || type == "number" || type == "string" && value.length != 0;
+	  });
+	
+	  let properties = object.preview ? object.preview.ownProperties : {};
+	  let propertiesLength = object.preview && object.preview.ownPropertiesLength ? object.preview.ownPropertiesLength : object.ownPropertyLength;
+	
+	  if (object.preview && object.preview.safeGetterValues) {
+	    properties = Object.assign({}, properties, object.preview.safeGetterValues);
+	    propertiesLength += Object.keys(object.preview.safeGetterValues).length;
+	  }
+	
+	  let indexes = getPropIndexes(properties, max, isInterestingProp);
+	  if (indexes.length < max && indexes.length < propertiesLength) {
+	    // There are not enough props yet. Then add uninteresting props to display them.
+	    indexes = indexes.concat(getPropIndexes(properties, max - indexes.length, (t, value, name) => {
+	      return !isInterestingProp(t, value, name);
+	    }));
+	  }
+	
+	  const truncate = Object.keys(properties).length > max;
+	  // 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 = getProps(props, properties, indexes, truncate, suppressQuotes);
+	  if (truncate) {
+	    // There are some undisplayed props. Then display "more...".
+	    propsArray.push(Caption({
+	      object: safeObjectLink(props, {}, `${propertiesLength - max} more…`)
+	    }));
+	  }
+	
+	  return propsArray;
+	}
+	
+	/**
+	 * Get props ordered by index.
+	 *
+	 * @param {Object} componentProps Grip Component props.
+	 * @param {Object} properties Properties of the object the Grip describes.
+	 * @param {Array} indexes Indexes of properties.
+	 * @param {Boolean} truncate true if the grip will be truncated.
+	 * @param {Boolean} suppressQuotes true if we should suppress quotes
+	 *                  on property names.
+	 * @return {Array} Props.
+	 */
+	function getProps(componentProps, properties, indexes, truncate, suppressQuotes) {
+	  // Make indexes ordered by ascending.
+	  indexes.sort(function (a, b) {
+	    return a - b;
+	  });
+	
+	  const propertiesKeys = Object.keys(properties);
+	  return indexes.map(i => {
+	    let name = propertiesKeys[i];
+	    let value = getPropValue(properties[name]);
+	
+	    return PropRep(Object.assign({}, componentProps, {
+	      mode: MODE.TINY,
+	      name,
+	      object: value,
+	      equal: ": ",
+	      delim: i !== indexes.length - 1 || truncate ? ", " : null,
+	      defaultRep: Grip,
+	      // Do not propagate title and objectLink to properties reps
+	      title: null,
+	      objectLink: null,
+	      suppressQuotes
+	    }));
+	  });
+	}
+	
+	/**
+	 * Get the indexes of props in the object.
+	 *
+	 * @param {Object} properties Props object.
+	 * @param {Number} max The maximum length of indexes array.
+	 * @param {Function} filter Filter the props you want.
+	 * @return {Array} Indexes of interesting props in the object.
+	 */
+	function getPropIndexes(properties, max, filter) {
+	  let indexes = [];
+	
+	  try {
+	    let i = 0;
+	    for (let name in properties) {
+	      if (indexes.length >= max) {
+	        return indexes;
+	      }
+	
+	      // Type is specified in grip's "class" field and for primitive
+	      // values use typeof.
+	      let value = getPropValue(properties[name]);
+	      let type = value.class || typeof value;
+	      type = type.toLowerCase();
+	
+	      if (filter(type, value, name)) {
+	        indexes.push(i);
+	      }
+	      i++;
 	    }
-	
-	    return propsArray;
-	  },
-	
-	  /**
-	   * Get props ordered by index.
-	   *
-	   * @param {Object} properties Props object.
-	   * @param {Array} indexes Indexes of props.
-	   * @param {Boolean} truncate true if the grip will be truncated.
-	   * @param {Boolean} suppressQuotes true if we should suppress quotes
-	   *                  on property names.
-	   * @return {Array} Props.
-	   */
-	  getProps: function (properties, indexes, truncate, suppressQuotes) {
-	    let propsArray = [];
-	
-	    // Make indexes ordered by ascending.
-	    indexes.sort(function (a, b) {
-	      return a - b;
-	    });
-	
-	    indexes.forEach(i => {
-	      let name = Object.keys(properties)[i];
-	      let value = this.getPropValue(properties[name]);
-	
-	      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.
-	   *
-	   * @param {Object} properties Props object.
-	   * @param {Number} max The maximum length of indexes array.
-	   * @param {Function} filter Filter the props you want.
-	   * @return {Array} Indexes of interesting props in the object.
-	   */
-	  getPropIndexes: function (properties, max, filter) {
-	    let indexes = [];
-	
-	    try {
-	      let i = 0;
-	      for (let name in properties) {
-	        if (indexes.length >= max) {
-	          return indexes;
-	        }
-	
-	        // Type is specified in grip's "class" field and for primitive
-	        // values use typeof.
-	        let value = this.getPropValue(properties[name]);
-	        let type = value.class || typeof value;
-	        type = type.toLowerCase();
-	
-	        if (filter(type, value, name)) {
-	          indexes.push(i);
-	        }
-	        i++;
-	      }
-	    } catch (err) {
-	      console.error(err);
+	  } catch (err) {
+	    console.error(err);
+	  }
+	  return indexes;
+	}
+	
+	/**
+	 * Get the actual value of a property.
+	 *
+	 * @param {Object} property
+	 * @return {Object} Value of the property.
+	 */
+	function getPropValue(property) {
+	  let value = property;
+	  if (typeof property === "object") {
+	    let keys = Object.keys(property);
+	    if (keys.includes("value")) {
+	      value = property.value;
+	    } else if (keys.includes("getterValue")) {
+	      value = property.getterValue;
 	    }
-	    return indexes;
-	  },
-	
-	  /**
-	   * Get the actual value of a property.
-	   *
-	   * @param {Object} property
-	   * @return {Object} Value of the property.
-	   */
-	  getPropValue: function (property) {
-	    let value = property;
-	    if (typeof property === "object") {
-	      let keys = Object.keys(property);
-	      if (keys.includes("value")) {
-	        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);
-	
-	    if (this.props.mode === MODE.TINY) {
-	      return span({ className: "objectBox objectBox-object" }, this.getTitle(object));
-	    }
-	
-	    return span({ className: "objectBox objectBox-object" }, this.getTitle(object), this.safeObjectLink({
-	      className: "objectLeftBrace"
-	    }, " { "), ...propsArray, this.safeObjectLink({
-	      className: "objectRightBrace"
-	    }, " }"));
-	  })
-	});
+	  }
+	  return value;
+	}
 	
 	// Registration
 	function supportsObject(object, type) {
 	  if (!isGrip(object)) {
 	    return false;
 	  }
 	  return object.preview && object.preview.ownProperties;
 	}
 	
 	// Grip is used in propIterator and has to be defined here.
 	let Grip = {
-	  rep: GripRep,
-	  supportsObject: supportsObject
+	  rep: wrapRender(GripRep),
+	  supportsObject
 	};
 	
 	// Exports from this module
 	module.exports = Grip;
 
 /***/ },
 /* 16 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
-	const React = __webpack_require__(3);
-	
-	const { wrapRender } = __webpack_require__(4);
+	const React = __webpack_require__(4);
+	
+	const { wrapRender } = __webpack_require__(3);
 	
 	// Shortcuts
 	const { span } = React.DOM;
 	
 	/**
 	 * Renders a symbol.
 	 */
-	const SymbolRep = React.createClass({
-	  displayName: "SymbolRep",
-	
-	  propTypes: {
-	    object: React.PropTypes.object.isRequired
-	  },
-	
-	  render: wrapRender(function () {
-	    let { object } = this.props;
-	    let { name } = object;
-	
-	    return span({ className: "objectBox objectBox-symbol" }, `Symbol(${name || ""})`);
-	  })
-	});
+	SymbolRep.propTypes = {
+	  object: React.PropTypes.object.isRequired
+	};
+	
+	function SymbolRep(props) {
+	  let { object } = props;
+	  let { name } = object;
+	
+	  return span({ className: "objectBox objectBox-symbol" }, `Symbol(${name || ""})`);
+	}
 	
 	function supportsObject(object, type) {
 	  return type == "symbol";
 	}
 	
 	// Exports from this module
 	module.exports = {
-	  rep: SymbolRep,
-	  supportsObject: supportsObject
+	  rep: wrapRender(SymbolRep),
+	  supportsObject
 	};
 
 /***/ },
 /* 17 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
-	const React = __webpack_require__(3);
-	
-	const { wrapRender } = __webpack_require__(4);
+	const React = __webpack_require__(4);
+	
+	const { wrapRender } = __webpack_require__(3);
 	
 	// Shortcuts
 	const { span } = React.DOM;
 	
 	/**
 	 * Renders a Infinity object
 	 */
-	const InfinityRep = React.createClass({
-	  displayName: "Infinity",
-	
-	  propTypes: {
-	    object: React.PropTypes.object.isRequired
-	  },
-	
-	  render: wrapRender(function () {
-	    return span({ className: "objectBox objectBox-number" }, this.props.object.type);
-	  })
-	});
+	InfinityRep.propTypes = {
+	  object: React.PropTypes.object.isRequired
+	};
+	
+	function InfinityRep(props) {
+	  return span({ className: "objectBox objectBox-number" }, props.object.type);
+	}
 	
 	function supportsObject(object, type) {
 	  return type == "Infinity" || type == "-Infinity";
 	}
 	
 	// Exports from this module
 	module.exports = {
-	  rep: InfinityRep,
-	  supportsObject: supportsObject
+	  rep: wrapRender(InfinityRep),
+	  supportsObject
 	};
 
 /***/ },
 /* 18 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
-	const React = __webpack_require__(3);
-	
-	const { wrapRender } = __webpack_require__(4);
+	const React = __webpack_require__(4);
+	
+	const { wrapRender } = __webpack_require__(3);
 	
 	// Shortcuts
 	const { span } = React.DOM;
 	
 	/**
 	 * Renders a NaN object
 	 */
-	const NaNRep = React.createClass({
-	  displayName: "NaN",
-	
-	  render: wrapRender(function () {
-	    return span({ className: "objectBox objectBox-nan" }, "NaN");
-	  })
-	});
+	function NaNRep(props) {
+	  return span({ className: "objectBox objectBox-nan" }, "NaN");
+	}
 	
 	function supportsObject(object, type) {
 	  return type == "NaN";
 	}
 	
 	// Exports from this module
 	module.exports = {
-	  rep: NaNRep,
-	  supportsObject: supportsObject
+	  rep: wrapRender(NaNRep),
+	  supportsObject
 	};
 
 /***/ },
 /* 19 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// ReactJS
-	const React = __webpack_require__(3);
+	const React = __webpack_require__(4);
 	
 	// Reps
 	const {
-	  createFactories,
 	  isGrip,
+	  safeObjectLink,
 	  wrapRender
-	} = __webpack_require__(4);
-	const StringRep = __webpack_require__(8);
+	} = __webpack_require__(3);
+	const { rep: StringRep } = __webpack_require__(8);
 	
 	// Shortcuts
 	const { span } = React.DOM;
-	const { rep: StringRepFactory } = createFactories(StringRep);
 	
 	/**
 	 * Renders DOM attribute
 	 */
-	let Attribute = React.createClass({
-	  displayName: "Attr",
-	
-	  propTypes: {
-	    object: React.PropTypes.object.isRequired,
-	    objectLink: React.PropTypes.func
-	  },
-	
-	  getTitle: function (grip) {
-	    return grip.preview.nodeName;
-	  },
-	
-	  render: wrapRender(function () {
-	    let object = this.props.object;
-	    let value = object.preview.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 }));
-	  })
-	});
+	Attribute.propTypes = {
+	  object: React.PropTypes.object.isRequired,
+	  objectLink: React.PropTypes.func
+	};
+	
+	function Attribute(props) {
+	  let {
+	    object
+	  } = props;
+	  let value = object.preview.value;
+	
+	  return safeObjectLink(props, { className: "objectLink-Attr" }, span({ className: "attrTitle" }, getTitle(object)), span({ className: "attrEqual" }, "="), StringRep({ object: value }));
+	}
+	
+	function getTitle(grip) {
+	  return grip.preview.nodeName;
+	}
 	
 	// Registration
-	
 	function supportsObject(grip, type) {
 	  if (!isGrip(grip)) {
 	    return false;
 	  }
 	
 	  return type == "Attr" && grip.preview;
 	}
 	
 	module.exports = {
-	  rep: Attribute,
-	  supportsObject: supportsObject
+	  rep: wrapRender(Attribute),
+	  supportsObject
 	};
 
 /***/ },
 /* 20 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// ReactJS
-	const React = __webpack_require__(3);
+	const React = __webpack_require__(4);
 	
 	// Reps
 	const {
 	  isGrip,
+	  safeObjectLink,
 	  wrapRender
-	} = __webpack_require__(4);
+	} = __webpack_require__(3);
 	
 	// 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,
-	    objectLink: React.PropTypes.func
-	  },
-	
-	  getTitle: function (grip) {
-	    if (this.props.objectLink) {
-	      return this.props.objectLink({
-	        object: grip
-	      }, grip.class + " ");
-	    }
-	    return "";
-	  },
-	
-	  render: wrapRender(function () {
-	    let grip = this.props.object;
-	    let date;
-	    try {
-	      date = span({ className: "objectBox" }, this.getTitle(grip), span({ className: "Date" }, new Date(grip.preview.timestamp).toISOString()));
-	    } catch (e) {
-	      date = span({ className: "objectBox" }, "Invalid Date");
-	    }
-	
-	    return date;
-	  })
-	});
+	DateTime.propTypes = {
+	  object: React.PropTypes.object.isRequired,
+	  objectLink: React.PropTypes.func
+	};
+	
+	function DateTime(props) {
+	  let grip = props.object;
+	  let date;
+	  try {
+	    date = span({ className: "objectBox" }, getTitle(props, grip), span({ className: "Date" }, new Date(grip.preview.timestamp).toISOString()));
+	  } catch (e) {
+	    date = span({ className: "objectBox" }, "Invalid Date");
+	  }
+	
+	  return date;
+	}
+	
+	function getTitle(props, grip) {
+	  return safeObjectLink(props, {}, grip.class + " ");
+	}
 	
 	// Registration
-	
 	function supportsObject(grip, type) {
 	  if (!isGrip(grip)) {
 	    return false;
 	  }
 	
 	  return type == "Date" && grip.preview;
 	}
 	
 	// Exports from this module
 	module.exports = {
-	  rep: DateTime,
-	  supportsObject: supportsObject
+	  rep: wrapRender(DateTime),
+	  supportsObject
 	};
 
 /***/ },
 /* 21 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// ReactJS
-	const React = __webpack_require__(3);
+	const React = __webpack_require__(4);
 	
 	// Reps
 	const {
 	  isGrip,
 	  getURLDisplayString,
+	  safeObjectLink,
 	  wrapRender
-	} = __webpack_require__(4);
+	} = __webpack_require__(3);
 	
 	// Shortcuts
 	const { span } = React.DOM;
 	
 	/**
 	 * Renders DOM document object.
 	 */
-	let Document = React.createClass({
-	  displayName: "Document",
-	
-	  propTypes: {
-	    object: React.PropTypes.object.isRequired,
-	    objectLink: React.PropTypes.func
-	  },
-	
-	  getLocation: function (grip) {
-	    let location = grip.preview.location;
-	    return location ? getURLDisplayString(location) : "";
-	  },
-	
-	  getTitle: function (grip) {
-	    if (this.props.objectLink) {
-	      return span({ className: "objectBox" }, this.props.objectLink({
-	        object: grip
-	      }, grip.class + " "));
-	    }
-	    return "";
-	  },
-	
-	  getTooltip: function (doc) {
-	    return doc.location.href;
-	  },
-	
-	  render: wrapRender(function () {
-	    let grip = this.props.object;
-	
-	    return span({ className: "objectBox objectBox-object" }, this.getTitle(grip), span({ className: "objectPropValue" }, this.getLocation(grip)));
-	  })
-	});
+	Document.propTypes = {
+	  object: React.PropTypes.object.isRequired,
+	  objectLink: React.PropTypes.func
+	};
+	
+	function Document(props) {
+	  let grip = props.object;
+	
+	  return span({ className: "objectBox objectBox-object" }, getTitle(props, grip), span({ className: "objectPropValue" }, getLocation(grip)));
+	}
+	
+	function getLocation(grip) {
+	  let location = grip.preview.location;
+	  return location ? getURLDisplayString(location) : "";
+	}
+	
+	function getTitle(props, grip) {
+	  return safeObjectLink(props, {}, grip.class + " ");
+	}
 	
 	// Registration
-	
 	function supportsObject(object, type) {
 	  if (!isGrip(object)) {
 	    return false;
 	  }
 	
 	  return object.preview && type == "HTMLDocument";
 	}
 	
 	// Exports from this module
 	module.exports = {
-	  rep: Document,
-	  supportsObject: supportsObject
+	  rep: wrapRender(Document),
+	  supportsObject
 	};
 
 /***/ },
 /* 22 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// ReactJS
-	const React = __webpack_require__(3);
+	const React = __webpack_require__(4);
 	
 	// Reps
 	const {
-	  createFactories,
 	  isGrip,
 	  wrapRender
-	} = __webpack_require__(4);
-	
-	const { rep } = createFactories(__webpack_require__(15));
+	} = __webpack_require__(3);
+	
 	const { MODE } = __webpack_require__(1);
+	const { rep } = __webpack_require__(15);
 	
 	/**
 	 * Renders DOM event objects.
 	 */
-	let Event = React.createClass({
-	  displayName: "event",
-	
-	  propTypes: {
-	    object: React.PropTypes.object.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])),
-	    attachedActorIds: React.PropTypes.array,
-	    onDOMNodeMouseOver: React.PropTypes.func,
-	    onDOMNodeMouseOut: React.PropTypes.func,
-	    onInspectIconClick: React.PropTypes.func
-	  },
-	
-	  getTitle: function (props) {
-	    let preview = props.object.preview;
-	    let title = preview.type;
-	
-	    if (preview.eventKind == "key" && preview.modifiers && preview.modifiers.length) {
-	      title = `${title} ${preview.modifiers.join("-")}`;
-	    }
-	    return title;
-	  },
-	
-	  render: wrapRender(function () {
-	    // Use `Object.assign` to keep `this.props` without changes because:
-	    // 1. JSON.stringify/JSON.parse is slow.
-	    // 2. Immutable.js is planned for the future.
-	    let gripProps = Object.assign({}, this.props, {
-	      title: this.getTitle(this.props)
+	Event.propTypes = {
+	  object: React.PropTypes.object.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])),
+	  onDOMNodeMouseOver: React.PropTypes.func,
+	  onDOMNodeMouseOut: React.PropTypes.func,
+	  onInspectIconClick: React.PropTypes.func
+	};
+	
+	function Event(props) {
+	  // Use `Object.assign` to keep `props` without changes because:
+	  // 1. JSON.stringify/JSON.parse is slow.
+	  // 2. Immutable.js is planned for the future.
+	  let gripProps = Object.assign({}, props, {
+	    title: getTitle(props)
+	  });
+	  gripProps.object = Object.assign({}, props.object);
+	  gripProps.object.preview = Object.assign({}, props.object.preview);
+	
+	  gripProps.object.preview.ownProperties = {};
+	  if (gripProps.object.preview.target) {
+	    Object.assign(gripProps.object.preview.ownProperties, {
+	      target: gripProps.object.preview.target
 	    });
-	    gripProps.object = Object.assign({}, this.props.object);
-	    gripProps.object.preview = Object.assign({}, this.props.object.preview);
-	
-	    gripProps.object.preview.ownProperties = {};
-	    if (gripProps.object.preview.target) {
-	      Object.assign(gripProps.object.preview.ownProperties, {
-	        target: gripProps.object.preview.target
-	      });
-	    }
-	    Object.assign(gripProps.object.preview.ownProperties, gripProps.object.preview.properties);
-	
-	    delete gripProps.object.preview.properties;
-	    gripProps.object.ownPropertyLength = Object.keys(gripProps.object.preview.ownProperties).length;
-	
-	    switch (gripProps.object.class) {
-	      case "MouseEvent":
-	        gripProps.isInterestingProp = (type, value, name) => {
-	          return ["target", "clientX", "clientY", "layerX", "layerY"].includes(name);
-	        };
-	        break;
-	      case "KeyboardEvent":
-	        gripProps.isInterestingProp = (type, value, name) => {
-	          return ["target", "key", "charCode", "keyCode"].includes(name);
-	        };
-	        break;
-	      case "MessageEvent":
-	        gripProps.isInterestingProp = (type, value, name) => {
-	          return ["target", "isTrusted", "data"].includes(name);
-	        };
-	        break;
-	      default:
-	        gripProps.isInterestingProp = (type, value, name) => {
-	          // We want to show the properties in the order they are declared.
-	          return Object.keys(gripProps.object.preview.ownProperties).includes(name);
-	        };
-	    }
-	
-	    return rep(gripProps);
-	  })
-	});
+	  }
+	  Object.assign(gripProps.object.preview.ownProperties, gripProps.object.preview.properties);
+	
+	  delete gripProps.object.preview.properties;
+	  gripProps.object.ownPropertyLength = Object.keys(gripProps.object.preview.ownProperties).length;
+	
+	  switch (gripProps.object.class) {
+	    case "MouseEvent":
+	      gripProps.isInterestingProp = (type, value, name) => {
+	        return ["target", "clientX", "clientY", "layerX", "layerY"].includes(name);
+	      };
+	      break;
+	    case "KeyboardEvent":
+	      gripProps.isInterestingProp = (type, value, name) => {
+	        return ["target", "key", "charCode", "keyCode"].includes(name);
+	      };
+	      break;
+	    case "MessageEvent":
+	      gripProps.isInterestingProp = (type, value, name) => {
+	        return ["target", "isTrusted", "data"].includes(name);
+	      };
+	      break;
+	    default:
+	      gripProps.isInterestingProp = (type, value, name) => {
+	        // We want to show the properties in the order they are declared.
+	        return Object.keys(gripProps.object.preview.ownProperties).includes(name);
+	      };
+	  }
+	
+	  return rep(gripProps);
+	}
+	
+	function getTitle(props) {
+	  let preview = props.object.preview;
+	  let title = preview.type;
+	
+	  if (preview.eventKind == "key" && preview.modifiers && preview.modifiers.length) {
+	    title = `${title} ${preview.modifiers.join("-")}`;
+	  }
+	  return title;
+	}
 	
 	// Registration
-	
 	function supportsObject(grip, type) {
 	  if (!isGrip(grip)) {
 	    return false;
 	  }
 	
 	  return grip.preview && grip.preview.kind == "DOMEvent";
 	}
 	
 	// Exports from this module
 	module.exports = {
-	  rep: Event,
-	  supportsObject: supportsObject
+	  rep: wrapRender(Event),
+	  supportsObject
 	};
 
 /***/ },
 /* 23 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// ReactJS
-	const React = __webpack_require__(3);
+	const React = __webpack_require__(4);
 	
 	// Reps
 	const {
 	  isGrip,
 	  cropString,
+	  safeObjectLink,
 	  wrapRender
-	} = __webpack_require__(4);
+	} = __webpack_require__(3);
 	
 	// Shortcuts
 	const { span } = React.DOM;
 	
 	/**
 	 * This component represents a template for Function objects.
 	 */
-	let Func = React.createClass({
-	  displayName: "Func",
-	
-	  propTypes: {
-	    object: React.PropTypes.object.isRequired,
-	    objectLink: React.PropTypes.func
-	  },
-	
-	  getTitle: function (grip) {
-	    let title = "function ";
-	    if (grip.isGenerator) {
-	      title = "function* ";
-	    }
-	    if (grip.isAsync) {
-	      title = "async " + title;
-	    }
-	
-	    if (this.props.objectLink) {
-	      return this.props.objectLink({
-	        object: grip
-	      }, title);
-	    }
-	
-	    return title;
-	  },
-	
-	  summarizeFunction: function (grip) {
-	    let name = grip.userDisplayName || grip.displayName || grip.name || "";
-	    return cropString(name + "()", 100);
-	  },
-	
-	  render: wrapRender(function () {
-	    let grip = this.props.object;
-	
-	    return (
-	      // Set dir="ltr" to prevent function parentheses from
-	      // appearing in the wrong direction
-	      span({ dir: "ltr", className: "objectBox objectBox-function" }, this.getTitle(grip), this.summarizeFunction(grip))
-	    );
-	  })
-	});
+	FunctionRep.propTypes = {
+	  object: React.PropTypes.object.isRequired,
+	  objectLink: React.PropTypes.func
+	};
+	
+	function FunctionRep(props) {
+	  let grip = props.object;
+	
+	  return (
+	    // Set dir="ltr" to prevent function parentheses from
+	    // appearing in the wrong direction
+	    span({ dir: "ltr", className: "objectBox objectBox-function" }, getTitle(props, grip), summarizeFunction(grip))
+	  );
+	}
+	
+	function getTitle(props, grip) {
+	  let title = "function ";
+	  if (grip.isGenerator) {
+	    title = "function* ";
+	  }
+	  if (grip.isAsync) {
+	    title = "async " + title;
+	  }
+	
+	  return safeObjectLink(props, {}, title);
+	}
+	
+	function summarizeFunction(grip) {
+	  let name = grip.userDisplayName || grip.displayName || grip.name || "";
+	  return cropString(name + "()", 100);
+	}
 	
 	// Registration
-	
 	function supportsObject(grip, type) {
 	  if (!isGrip(grip)) {
 	    return type == "function";
 	  }
 	
 	  return type == "Function";
 	}
 	
 	// Exports from this module
 	
 	module.exports = {
-	  rep: Func,
-	  supportsObject: supportsObject
+	  rep: wrapRender(FunctionRep),
+	  supportsObject
 	};
 
 /***/ },
 /* 24 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// ReactJS
-	const React = __webpack_require__(3);
+	const React = __webpack_require__(4);
 	// Dependencies
 	const {
-	  createFactories,
 	  isGrip,
+	  safeObjectLink,
 	  wrapRender
-	} = __webpack_require__(4);
-	
-	const PropRep = React.createFactory(__webpack_require__(14));
+	} = __webpack_require__(3);
+	
+	const PropRep = __webpack_require__(14);
 	const { MODE } = __webpack_require__(1);
 	// Shortcuts
 	const { span } = React.DOM;
 	
 	/**
 	 * Renders a DOM Promise object.
 	 */
-	const PromiseRep = React.createClass({
-	  displayName: "Promise",
-	
-	  propTypes: {
-	    object: React.PropTypes.object.isRequired,
-	    // @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,
-	    attachedActorIds: React.PropTypes.array,
-	    onDOMNodeMouseOver: React.PropTypes.func,
-	    onDOMNodeMouseOut: React.PropTypes.func,
-	    onInspectIconClick: React.PropTypes.func
-	  },
-	
-	  getTitle: function (object) {
-	    const title = object.class;
-	    return this.safeObjectLink({}, title);
-	  },
-	
-	  getProps: function (promiseState) {
-	    const keys = ["state"];
-	    if (Object.keys(promiseState).includes("value")) {
-	      keys.push("value");
-	    }
-	
-	    return keys.map((key, i) => {
-	      let object = promiseState[key];
-	      return PropRep(Object.assign({}, this.props, {
-	        mode: MODE.TINY,
-	        name: `<${key}>`,
-	        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;
-	
-	    if (this.props.mode === MODE.TINY) {
-	      let { Rep } = createFactories(__webpack_require__(2));
-	
-	      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), this.safeObjectLink({
+	PromiseRep.propTypes = {
+	  object: React.PropTypes.object.isRequired,
+	  // @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,
+	  onDOMNodeMouseOver: React.PropTypes.func,
+	  onDOMNodeMouseOut: React.PropTypes.func,
+	  onInspectIconClick: React.PropTypes.func
+	};
+	
+	function PromiseRep(props) {
+	  const object = props.object;
+	  const { promiseState } = object;
+	
+	  if (props.mode === MODE.TINY) {
+	    let { Rep } = __webpack_require__(2);
+	
+	    return span({ className: "objectBox objectBox-object" }, getTitle(props, object), safeObjectLink(props, {
 	      className: "objectLeftBrace"
-	    }, " { "), ...propsArray, this.safeObjectLink({
+	    }, " { "), Rep({ object: promiseState.state }), safeObjectLink(props, {
 	      className: "objectRightBrace"
 	    }, " }"));
-	  })
-	});
+	  }
+	
+	  const propsArray = getProps(props, promiseState);
+	  return span({ className: "objectBox objectBox-object" }, getTitle(props, object), safeObjectLink(props, {
+	    className: "objectLeftBrace"
+	  }, " { "), ...propsArray, safeObjectLink(props, {
+	    className: "objectRightBrace"
+	  }, " }"));
+	}
+	
+	function getTitle(props, object) {
+	  const title = object.class;
+	  return safeObjectLink(props, {}, title);
+	}
+	
+	function getProps(props, promiseState) {
+	  const keys = ["state"];
+	  if (Object.keys(promiseState).includes("value")) {
+	    keys.push("value");
+	  }
+	
+	  return keys.map((key, i) => {
+	    let object = promiseState[key];
+	    return PropRep(Object.assign({}, props, {
+	      mode: MODE.TINY,
+	      name: `<${key}>`,
+	      object,
+	      equal: ": ",
+	      delim: i < keys.length - 1 ? ", " : null,
+	      suppressQuotes: true
+	    }));
+	  });
+	}
 	
 	// Registration
 	function supportsObject(object, type) {
 	  if (!isGrip(object)) {
 	    return false;
 	  }
 	  return type === "Promise";
 	}
 	
 	// Exports from this module
 	module.exports = {
-	  rep: PromiseRep,
-	  supportsObject: supportsObject
+	  rep: wrapRender(PromiseRep),
+	  supportsObject
 	};
 
 /***/ },
 /* 25 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// ReactJS
-	const React = __webpack_require__(3);
+	const React = __webpack_require__(4);
 	
 	// Reps
 	const {
 	  isGrip,
+	  safeObjectLink,
 	  wrapRender
-	} = __webpack_require__(4);
-	
-	// Shortcuts
-	const { span } = React.DOM;
+	} = __webpack_require__(3);
 	
 	/**
 	 * Renders a grip object with regular expression.
 	 */
-	let RegExp = React.createClass({
-	  displayName: "regexp",
-	
-	  propTypes: {
-	    object: React.PropTypes.object.isRequired,
-	    objectLink: React.PropTypes.func
-	  },
-	
-	  getSource: function (grip) {
-	    return grip.displayString;
-	  },
-	
-	  render: wrapRender(function () {
-	    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));
-	  })
-	});
+	RegExp.propTypes = {
+	  object: React.PropTypes.object.isRequired,
+	  objectLink: React.PropTypes.func
+	};
+	
+	function RegExp(props) {
+	  let { object } = props;
+	
+	  return safeObjectLink(props, {
+	    className: "objectBox objectBox-regexp regexpSource"
+	  }, getSource(object));
+	}
+	
+	function getSource(grip) {
+	  return grip.displayString;
+	}
 	
 	// Registration
-	
 	function supportsObject(object, type) {
 	  if (!isGrip(object)) {
 	    return false;
 	  }
 	
 	  return type == "RegExp";
 	}
 	
 	// Exports from this module
 	module.exports = {
-	  rep: RegExp,
-	  supportsObject: supportsObject
+	  rep: wrapRender(RegExp),
+	  supportsObject
 	};
 
 /***/ },
 /* 26 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// ReactJS
-	const React = __webpack_require__(3);
+	const React = __webpack_require__(4);
 	
 	// Reps
 	const {
 	  isGrip,
 	  getURLDisplayString,
+	  safeObjectLink,
 	  wrapRender
-	} = __webpack_require__(4);
+	} = __webpack_require__(3);
 	
 	// Shortcuts
-	const DOM = React.DOM;
+	const { span } = React.DOM;
 	
 	/**
 	 * Renders a grip representing CSSStyleSheet
 	 */
-	let StyleSheet = React.createClass({
-	  displayName: "object",
-	
-	  propTypes: {
-	    object: React.PropTypes.object.isRequired,
-	    objectLink: React.PropTypes.func
-	  },
-	
-	  getTitle: function (grip) {
-	    let title = "StyleSheet ";
-	    if (this.props.objectLink) {
-	      return DOM.span({ className: "objectBox" }, this.props.objectLink({
-	        object: 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 ? getURLDisplayString(url) : "";
-	  },
-	
-	  render: wrapRender(function () {
-	    let grip = this.props.object;
-	
-	    return DOM.span({ className: "objectBox objectBox-object" }, this.getTitle(grip), DOM.span({ className: "objectPropValue" }, this.getLocation(grip)));
-	  })
-	});
+	StyleSheet.propTypes = {
+	  object: React.PropTypes.object.isRequired,
+	  objectLink: React.PropTypes.func
+	};
+	
+	function StyleSheet(props) {
+	  let grip = props.object;
+	
+	  return span({ className: "objectBox objectBox-object" }, getTitle(props, grip), span({ className: "objectPropValue" }, getLocation(grip)));
+	}
+	
+	function getTitle(props, grip) {
+	  let title = "StyleSheet ";
+	  return safeObjectLink(props, { className: "objectBox" }, title);
+	}
+	
+	function getLocation(grip) {
+	  // Embedded stylesheets don't have URL and so, no preview.
+	  let url = grip.preview ? grip.preview.url : "";
+	  return url ? getURLDisplayString(url) : "";
+	}
 	
 	// Registration
-	
 	function supportsObject(object, type) {
 	  if (!isGrip(object)) {
 	    return false;
 	  }
 	
 	  return type == "CSSStyleSheet";
 	}
 	
 	// Exports from this module
 	
 	module.exports = {
-	  rep: StyleSheet,
-	  supportsObject: supportsObject
+	  rep: wrapRender(StyleSheet),
+	  supportsObject
 	};
 
 /***/ },
 /* 27 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
-	const React = __webpack_require__(3);
+	const React = __webpack_require__(4);
 	const {
 	  isGrip,
 	  cropString,
 	  cropMultipleLines,
 	  wrapRender
-	} = __webpack_require__(4);
+	} = __webpack_require__(3);
 	const { MODE } = __webpack_require__(1);
 	const nodeConstants = __webpack_require__(5);
 	
 	// Shortcuts
 	const { span } = React.DOM;
 	
 	/**
 	 * Renders DOM comment node.
 	 */
-	const CommentNode = React.createClass({
-	  displayName: "CommentNode",
-	
-	  propTypes: {
-	    object: React.PropTypes.object.isRequired,
-	    // @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]))
-	  },
-	
-	  render: wrapRender(function () {
-	    let {
-	      object,
-	      mode = MODE.SHORT
-	    } = this.props;
-	
-	    let { textContent } = object.preview;
-	    if (mode === MODE.TINY) {
-	      textContent = cropMultipleLines(textContent, 30);
-	    } else if (mode === MODE.SHORT) {
-	      textContent = cropString(textContent, 50);
-	    }
-	
-	    return span({ className: "objectBox theme-comment" }, `<!-- ${textContent} -->`);
-	  })
-	});
+	CommentNode.propTypes = {
+	  object: React.PropTypes.object.isRequired,
+	  // @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]))
+	};
+	
+	function CommentNode(props) {
+	  let {
+	    object,
+	    mode = MODE.SHORT
+	  } = props;
+	
+	  let { textContent } = object.preview;
+	  if (mode === MODE.TINY) {
+	    textContent = cropMultipleLines(textContent, 30);
+	  } else if (mode === MODE.SHORT) {
+	    textContent = cropString(textContent, 50);
+	  }
+	
+	  return span({ className: "objectBox theme-comment" }, `<!-- ${textContent} -->`);
+	}
 	
 	// Registration
 	function supportsObject(object, type) {
 	  if (!isGrip(object)) {
 	    return false;
 	  }
 	  return object.preview && object.preview.nodeType === nodeConstants.COMMENT_NODE;
 	}
 	
 	// Exports from this module
 	module.exports = {
-	  rep: CommentNode,
-	  supportsObject: supportsObject
+	  rep: wrapRender(CommentNode),
+	  supportsObject
 	};
 
 /***/ },
 /* 28 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// ReactJS
-	const React = __webpack_require__(3);
+	const React = __webpack_require__(4);
 	
 	// Utils
 	const {
 	  isGrip,
+	  safeObjectLink,
 	  wrapRender
-	} = __webpack_require__(4);
+	} = __webpack_require__(3);
 	const { MODE } = __webpack_require__(1);
 	const nodeConstants = __webpack_require__(5);
 	const Svg = __webpack_require__(29);
 	
 	// Shortcuts
 	const { span } = React.DOM;
 	
 	/**
 	 * Renders DOM element node.
 	 */
-	const ElementNode = React.createClass({
-	  displayName: "ElementNode",
-	
-	  propTypes: {
-	    object: React.PropTypes.object.isRequired,
-	    // @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])),
-	    attachedActorIds: React.PropTypes.array,
-	    onDOMNodeMouseOver: React.PropTypes.func,
-	    onDOMNodeMouseOut: React.PropTypes.func,
-	    onInspectIconClick: React.PropTypes.func,
-	    objectLink: React.PropTypes.func
-	  },
-	
-	  getElements: function (grip, mode) {
-	    let { attributes, nodeName } = grip.preview;
-	    const nodeNameElement = span({
-	      className: "tag-name theme-fg-color3"
-	    }, nodeName);
-	
-	    if (mode === MODE.TINY) {
-	      let elements = [nodeNameElement];
-	      if (attributes.id) {
-	        elements.push(span({ className: "attr-name theme-fg-color2" }, `#${attributes.id}`));
-	      }
-	      if (attributes.class) {
-	        elements.push(span({ className: "attr-name theme-fg-color2" }, attributes.class.replace(/(^\s+)|(\s+$)/g, "").split(" ").map(cls => `.${cls}`).join("")));
-	      }
-	      return elements;
+	ElementNode.propTypes = {
+	  object: React.PropTypes.object.isRequired,
+	  // @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])),
+	  onDOMNodeMouseOver: React.PropTypes.func,
+	  onDOMNodeMouseOut: React.PropTypes.func,
+	  onInspectIconClick: React.PropTypes.func,
+	  objectLink: React.PropTypes.func
+	};
+	
+	function ElementNode(props) {
+	  let {
+	    object,
+	    mode,
+	    onDOMNodeMouseOver,
+	    onDOMNodeMouseOut,
+	    onInspectIconClick
+	  } = props;
+	  let elements = getElements(object, mode);
+	
+	  let isInTree = object.preview && object.preview.isConnected === true;
+	
+	  let baseConfig = { className: "objectBox objectBox-node" };
+	  let inspectIcon;
+	  if (isInTree) {
+	    if (onDOMNodeMouseOver) {
+	      Object.assign(baseConfig, {
+	        onMouseOver: _ => onDOMNodeMouseOver(object)
+	      });
+	    }
+	
+	    if (onDOMNodeMouseOut) {
+	      Object.assign(baseConfig, {
+	        onMouseOut: onDOMNodeMouseOut
+	      });
 	    }
-	    let attributeElements = Object.keys(attributes).sort(function getIdAndClassFirst(a1, a2) {
-	      if ([a1, a2].includes("id")) {
-	        return 3 * (a1 === "id" ? -1 : 1);
-	      }
-	      if ([a1, a2].includes("class")) {
-	        return 2 * (a1 === "class" ? -1 : 1);
-	      }
-	
-	      // `id` and `class` excepted,
-	      // we want to keep the same order that in `attributes`.
-	      return 0;
-	    }).reduce((arr, name, i, keys) => {
-	      let value = attributes[name];
-	      let attribute = span({}, span({ className: "attr-name theme-fg-color2" }, `${name}`), `="`, span({ className: "attr-value theme-fg-color6" }, `${value}`), `"`);
-	
-	      return arr.concat([" ", attribute]);
-	    }, []);
-	
-	    return ["<", nodeNameElement, ...attributeElements, ">"];
-	  },
-	
-	  render: wrapRender(function () {
-	    let {
-	      object,
-	      mode,
-	      attachedActorIds,
-	      onDOMNodeMouseOver,
-	      onDOMNodeMouseOut,
-	      onInspectIconClick
-	    } = this.props;
-	    let elements = this.getElements(object, mode);
-	    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, {
-	          onMouseOver: _ => onDOMNodeMouseOver(object)
-	        });
-	      }
-	
-	      if (onDOMNodeMouseOut) {
-	        Object.assign(baseConfig, {
-	          onMouseOut: onDOMNodeMouseOut
-	        });
-	      }
-	
-	      if (onInspectIconClick) {
-	        inspectIcon = Svg("open-inspector", {
-	          element: "a",
-	          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)
-	        });
-	      }
+	
+	    if (onInspectIconClick) {
+	      inspectIcon = Svg("open-inspector", {
+	        element: "a",
+	        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, safeObjectLink(props, {}, ...elements), inspectIcon);
+	}
+	
+	function getElements(grip, mode) {
+	  let { attributes, nodeName } = grip.preview;
+	  const nodeNameElement = span({
+	    className: "tag-name theme-fg-color3"
+	  }, nodeName);
+	
+	  if (mode === MODE.TINY) {
+	    let elements = [nodeNameElement];
+	    if (attributes.id) {
+	      elements.push(span({ className: "attr-name theme-fg-color2" }, `#${attributes.id}`));
 	    }
-	
-	    return span(baseConfig, objectLink({}, ...elements), inspectIcon);
-	  })
-	});
+	    if (attributes.class) {
+	      elements.push(span({ className: "attr-name theme-fg-color2" }, attributes.class.replace(/(^\s+)|(\s+$)/g, "").split(" ").map(cls => `.${cls}`).join("")));
+	    }
+	    return elements;
+	  }
+	  let attributeElements = Object.keys(attributes).sort(function getIdAndClassFirst(a1, a2) {
+	    if ([a1, a2].includes("id")) {
+	      return 3 * (a1 === "id" ? -1 : 1);
+	    }
+	    if ([a1, a2].includes("class")) {
+	      return 2 * (a1 === "class" ? -1 : 1);
+	    }
+	
+	    // `id` and `class` excepted,
+	    // we want to keep the same order that in `attributes`.
+	    return 0;
+	  }).reduce((arr, name, i, keys) => {
+	    let value = attributes[name];
+	    let attribute = span({}, span({ className: "attr-name theme-fg-color2" }, `${name}`), `="`, span({ className: "attr-value theme-fg-color6" }, `${value}`), `"`);
+	
+	    return arr.concat([" ", attribute]);
+	  }, []);
+	
+	  return ["<", nodeNameElement, ...attributeElements, ">"];
+	}
 	
 	// Registration
 	function supportsObject(object, type) {
 	  if (!isGrip(object)) {
 	    return false;
 	  }
 	  return object.preview && object.preview.nodeType === nodeConstants.ELEMENT_NODE;
 	}
 	
 	// Exports from this module
 	module.exports = {
-	  rep: ElementNode,
-	  supportsObject: supportsObject
+	  rep: wrapRender(ElementNode),
+	  supportsObject
 	};
 
 /***/ },
 /* 29 */
 /***/ function(module, exports, __webpack_require__) {
 
-	const React = __webpack_require__(3);
+	const React = __webpack_require__(4);
 	const InlineSVG = __webpack_require__(30);
 	
 	const svg = {
 	  "open-inspector": __webpack_require__(31)
 	};
 	
 	module.exports = function (name, props) {
 	  // eslint-disable-line
@@ -2605,17 +2397,17 @@ return /******/ (function(modules) { // 
 	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
 	
 	function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
 	
 	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
 	
 	function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
 	
-	var _react = __webpack_require__(3);
+	var _react = __webpack_require__(4);
 	
 	var _react2 = _interopRequireDefault(_react);
 	
 	var DOMParser = typeof window !== 'undefined' && window.DOMParser;
 	var process = process || {};
 	process.env = process.env || {};
 	var parserAvailable = typeof DOMParser !== 'undefined' && DOMParser.prototype != null && DOMParser.prototype.parseFromString != null;
 	
@@ -2744,792 +2536,707 @@ return /******/ (function(modules) { // 
 
 	module.exports = "<!-- This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. --><svg viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M8,3L12,3L12,7L14,7L14,8L12,8L12,12L8,12L8,14L7,14L7,12L3,12L3,8L1,8L1,7L3,7L3,3L7,3L7,1L8,1L8,3ZM10,10L10,5L5,5L5,10L10,10Z\"></path></svg>"
 
 /***/ },
 /* 32 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// ReactJS
-	const React = __webpack_require__(3);
+	const React = __webpack_require__(4);
 	
 	// Reps
 	const {
 	  isGrip,
 	  cropString,
+	  safeObjectLink,
 	  wrapRender
-	} = __webpack_require__(4);
+	} = __webpack_require__(3);
 	const { MODE } = __webpack_require__(1);
 	const Svg = __webpack_require__(29);
 	
 	// Shortcuts
 	const DOM = React.DOM;
 	
 	/**
 	 * Renders DOM #text node.
 	 */
-	let TextNode = React.createClass({
-	  displayName: "TextNode",
-	
-	  propTypes: {
-	    object: React.PropTypes.object.isRequired,
-	    // @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,
-	    attachedActorIds: React.PropTypes.array,
-	    onDOMNodeMouseOver: React.PropTypes.func,
-	    onDOMNodeMouseOut: React.PropTypes.func,
-	    onInspectIconClick: React.PropTypes.func
-	  },
-	
-	  getTextContent: function (grip) {
-	    return cropString(grip.preview.textContent);
-	  },
-	
-	  getTitle: function (grip) {
-	    const title = "#text";
-	    if (this.props.objectLink) {
-	      return this.props.objectLink({
-	        object: grip
-	      }, title);
+	TextNode.propTypes = {
+	  object: React.PropTypes.object.isRequired,
+	  // @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,
+	  onDOMNodeMouseOver: React.PropTypes.func,
+	  onDOMNodeMouseOut: React.PropTypes.func,
+	  onInspectIconClick: React.PropTypes.func
+	};
+	
+	function TextNode(props) {
+	  let {
+	    object: grip,
+	    mode = MODE.SHORT,
+	    onDOMNodeMouseOver,
+	    onDOMNodeMouseOut,
+	    onInspectIconClick
+	  } = props;
+	
+	  let baseConfig = { className: "objectBox objectBox-textNode" };
+	  let inspectIcon;
+	  let isInTree = grip.preview && grip.preview.isConnected === true;
+	
+	  if (isInTree) {
+	    if (onDOMNodeMouseOver) {
+	      Object.assign(baseConfig, {
+	        onMouseOver: _ => onDOMNodeMouseOver(grip)
+	      });
 	    }
-	    return title;
-	  },
-	
-	  render: wrapRender(function () {
-	    let {
-	      object: grip,
-	      mode = MODE.SHORT,
-	      attachedActorIds,
-	      onDOMNodeMouseOver,
-	      onDOMNodeMouseOut,
-	      onInspectIconClick
-	    } = this.props;
-	
-	    let baseConfig = { className: "objectBox objectBox-textNode" };
-	    let inspectIcon;
-	    let isInTree = attachedActorIds ? attachedActorIds.includes(grip.actor) : true;
-	
-	    if (isInTree) {
-	      if (onDOMNodeMouseOver) {
-	        Object.assign(baseConfig, {
-	          onMouseOver: _ => onDOMNodeMouseOver(grip)
-	        });
-	      }
-	
-	      if (onDOMNodeMouseOut) {
-	        Object.assign(baseConfig, {
-	          onMouseOut: onDOMNodeMouseOut
-	        });
-	      }
-	
-	      if (onInspectIconClick) {
-	        inspectIcon = Svg("open-inspector", {
-	          element: "a",
-	          draggable: false,
-	          // TODO: Localize this with "openNodeInInspector" when Bug 1317038 lands
-	          title: "Click to select the node in the inspector",
-	          onClick: e => onInspectIconClick(grip, e)
-	        });
-	      }
+	
+	    if (onDOMNodeMouseOut) {
+	      Object.assign(baseConfig, {
+	        onMouseOut: onDOMNodeMouseOut
+	      });
+	    }
+	
+	    if (onInspectIconClick) {
+	      inspectIcon = Svg("open-inspector", {
+	        element: "a",
+	        draggable: false,
+	        // TODO: Localize this with "openNodeInInspector" when Bug 1317038 lands
+	        title: "Click to select the node in the inspector",
+	        onClick: e => onInspectIconClick(grip, e)
+	      });
 	    }
-	
-	    if (mode === MODE.TINY) {
-	      return DOM.span(baseConfig, this.getTitle(grip), inspectIcon);
-	    }
-	
-	    return DOM.span(baseConfig, this.getTitle(grip), DOM.span({ className: "nodeValue" }, " ", `"${this.getTextContent(grip)}"`), inspectIcon);
-	  })
-	});
+	  }
+	
+	  if (mode === MODE.TINY) {
+	    return DOM.span(baseConfig, getTitle(props, grip), inspectIcon);
+	  }
+	
+	  return DOM.span(baseConfig, getTitle(props, grip), DOM.span({ className: "nodeValue" }, " ", `"${getTextContent(grip)}"`), inspectIcon);
+	}
+	
+	function getTextContent(grip) {
+	  return cropString(grip.preview.textContent);
+	}
+	
+	function getTitle(props, grip) {
+	  const title = "#text";
+	  return safeObjectLink(props, {}, title);
+	}
 	
 	// Registration
-	
 	function supportsObject(grip, type) {
 	  if (!isGrip(grip)) {
 	    return false;
 	  }
 	
 	  return grip.preview && grip.class == "Text";
 	}
 	
 	// Exports from this module
 	module.exports = {
-	  rep: TextNode,
-	  supportsObject: supportsObject
+	  rep: wrapRender(TextNode),
+	  supportsObject
 	};
 
 /***/ },
 /* 33 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// ReactJS
-	const React = __webpack_require__(3);
+	const React = __webpack_require__(4);
 	// Utils
 	const {
 	  isGrip,
+	  safeObjectLink,
 	  wrapRender
-	} = __webpack_require__(4);
+	} = __webpack_require__(3);
 	const { MODE } = __webpack_require__(1);
-	// Shortcuts
-	const { span } = React.DOM;
 	
 	/**
 	 * Renders Error objects.
 	 */
-	const ErrorRep = React.createClass({
-	  displayName: "Error",
-	
-	  propTypes: {
-	    object: React.PropTypes.object.isRequired,
-	    // @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
-	  },
-	
-	  render: wrapRender(function () {
-	    let object = this.props.object;
-	    let preview = object.preview;
-	    let name = preview && preview.name ? preview.name : "Error";
-	
-	    let content = this.props.mode === MODE.TINY ? name : `${name}: ${preview.message}`;
-	
-	    if (preview.stack && this.props.mode !== MODE.TINY) {
-	      /*
-	       * 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 = (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);
-	  })
-	});
+	ErrorRep.propTypes = {
+	  object: React.PropTypes.object.isRequired,
+	  // @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
+	};
+	
+	function ErrorRep(props) {
+	  let object = props.object;
+	  let preview = object.preview;
+	  let name = preview && preview.name ? preview.name : "Error";
+	
+	  let content = props.mode === MODE.TINY ? name : `${name}: ${preview.message}`;
+	
+	  if (preview.stack && props.mode !== MODE.TINY) {
+	    /*
+	      * 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}`;
+	  }
+	
+	  return safeObjectLink(props, { className: "objectBox-stackTrace" }, content);
+	}
 	
 	// Registration
 	function supportsObject(object, type) {
 	  if (!isGrip(object)) {
 	    return false;
 	  }
 	  return object.preview && type === "Error";
 	}
 	
 	// Exports from this module
 	module.exports = {
-	  rep: ErrorRep,
-	  supportsObject: supportsObject
+	  rep: wrapRender(ErrorRep),
+	  supportsObject
 	};
 
 /***/ },
 /* 34 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// ReactJS
-	const React = __webpack_require__(3);
+	const React = __webpack_require__(4);
 	
 	// Reps
 	const {
 	  isGrip,
 	  getURLDisplayString,
+	  safeObjectLink,
 	  wrapRender
-	} = __webpack_require__(4);
+	} = __webpack_require__(3);
 	
 	const { MODE } = __webpack_require__(1);
 	
 	// Shortcuts
-	const DOM = React.DOM;
+	const { span } = React.DOM;
 	
 	/**
 	 * Renders a grip representing a window.
 	 */
-	let Window = React.createClass({
-	  displayName: "Window",
-	
-	  propTypes: {
-	    // @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])),
-	    object: React.PropTypes.object.isRequired,
-	    objectLink: React.PropTypes.func
-	  },
-	
-	  getTitle: function (object) {
-	    let title = object.displayClass || object.class || "Window";
-	    if (this.props.objectLink) {
-	      return DOM.span({ className: "objectBox" }, this.props.objectLink({
-	        object
-	      }, title));
-	    }
-	    return title;
-	  },
-	
-	  getLocation: function (object) {
-	    return getURLDisplayString(object.preview.url);
-	  },
-	
-	  render: wrapRender(function () {
-	    let {
-	      mode,
-	      object
-	    } = this.props;
-	
-	    if (mode === MODE.TINY) {
-	      return DOM.span({ className: "objectBox objectBox-Window" }, this.getTitle(object));
-	    }
-	
-	    return DOM.span({ className: "objectBox objectBox-Window" }, this.getTitle(object), " ", DOM.span({ className: "objectPropValue" }, this.getLocation(object)));
-	  })
-	});
+	WindowRep.propTypes = {
+	  // @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])),
+	  object: React.PropTypes.object.isRequired,
+	  objectLink: React.PropTypes.func
+	};
+	
+	function WindowRep(props) {
+	  let {
+	    mode,
+	    object
+	  } = props;
+	
+	  if (mode === MODE.TINY) {
+	    return span({ className: "objectBox objectBox-Window" }, getTitle(props, object));
+	  }
+	
+	  return span({ className: "objectBox objectBox-Window" }, getTitle(props, object), " ", span({ className: "objectPropValue" }, getLocation(object)));
+	}
+	
+	function getTitle(props, object) {
+	  let title = object.displayClass || object.class || "Window";
+	  return safeObjectLink(props, { className: "objectBox" }, title);
+	}
+	
+	function getLocation(object) {
+	  return getURLDisplayString(object.preview.url);
+	}
 	
 	// Registration
-	
 	function supportsObject(object, type) {
 	  if (!isGrip(object)) {
 	    return false;
 	  }
 	
 	  return object.preview && type == "Window";
 	}
 	
 	// Exports from this module
 	module.exports = {
-	  rep: Window,
-	  supportsObject: supportsObject
+	  rep: wrapRender(WindowRep),
+	  supportsObject
 	};
 
 /***/ },
 /* 35 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// ReactJS
-	const React = __webpack_require__(3);
+	const React = __webpack_require__(4);
 	
 	// Reps
 	const {
 	  isGrip,
 	  wrapRender
-	} = __webpack_require__(4);
+	} = __webpack_require__(3);
 	
 	// Shortcuts
 	const { span } = React.DOM;
 	
 	/**
 	 * Renders a grip object with textual data.
 	 */
-	let ObjectWithText = React.createClass({
-	  displayName: "ObjectWithText",
-	
-	  propTypes: {
-	    object: React.PropTypes.object.isRequired,
-	    objectLink: React.PropTypes.func
-	  },
-	
-	  getTitle: function (grip) {
-	    if (this.props.objectLink) {
-	      return span({ className: "objectBox" }, this.props.objectLink({
-	        object: grip
-	      }, this.getType(grip) + " "));
-	    }
-	    return "";
-	  },
-	
-	  getType: function (grip) {
-	    return grip.class;
-	  },
-	
-	  getDescription: function (grip) {
-	    return "\"" + grip.preview.text + "\"";
-	  },
-	
-	  render: wrapRender(function () {
-	    let grip = this.props.object;
-	    return span({ className: "objectBox objectBox-" + this.getType(grip) }, this.getTitle(grip), span({ className: "objectPropValue" }, this.getDescription(grip)));
-	  })
-	});
+	ObjectWithText.propTypes = {
+	  object: React.PropTypes.object.isRequired,
+	  objectLink: React.PropTypes.func
+	};
+	
+	function ObjectWithText(props) {
+	  let grip = props.object;
+	  return span({ className: "objectBox objectBox-" + getType(grip) }, getTitle(props, grip), span({ className: "objectPropValue" }, getDescription(grip)));
+	}
+	
+	function getTitle(props, grip) {
+	  if (props.objectLink) {
+	    return span({ className: "objectBox" }, props.objectLink({
+	      object: grip
+	    }, getType(grip) + " "));
+	  }
+	  return "";
+	}
+	
+	function getType(grip) {
+	  return grip.class;
+	}
+	
+	function getDescription(grip) {
+	  return "\"" + grip.preview.text + "\"";
+	}
 	
 	// Registration
-	
 	function supportsObject(grip, type) {
 	  if (!isGrip(grip)) {
 	    return false;
 	  }
 	
 	  return grip.preview && grip.preview.kind == "ObjectWithText";
 	}
 	
 	// Exports from this module
 	module.exports = {
-	  rep: ObjectWithText,
-	  supportsObject: supportsObject
+	  rep: wrapRender(ObjectWithText),
+	  supportsObject
 	};
 
 /***/ },
 /* 36 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// ReactJS
-	const React = __webpack_require__(3);
+	const React = __webpack_require__(4);
 	
 	// Reps
 	const {
 	  isGrip,
 	  getURLDisplayString,
+	  safeObjectLink,
 	  wrapRender
-	} = __webpack_require__(4);
+	} = __webpack_require__(3);
 	
 	// Shortcuts
 	const { span } = React.DOM;
 	
 	/**
 	 * Renders a grip object with URL data.
 	 */
-	let ObjectWithURL = React.createClass({
-	  displayName: "ObjectWithURL",
-	
-	  propTypes: {
-	    object: React.PropTypes.object.isRequired,
-	    objectLink: React.PropTypes.func
-	  },
-	
-	  getTitle: function (grip) {
-	    if (this.props.objectLink) {
-	      return span({ className: "objectBox" }, this.props.objectLink({
-	        object: grip
-	      }, this.getType(grip) + " "));
-	    }
-	    return "";
-	  },
-	
-	  getType: function (grip) {
-	    return grip.class;
-	  },
-	
-	  getDescription: function (grip) {
-	    return getURLDisplayString(grip.preview.url);
-	  },
-	
-	  render: wrapRender(function () {
-	    let grip = this.props.object;
-	    return span({ className: "objectBox objectBox-" + this.getType(grip) }, this.getTitle(grip), span({ className: "objectPropValue" }, this.getDescription(grip)));
-	  })
-	});
+	ObjectWithURL.propTypes = {
+	  object: React.PropTypes.object.isRequired,
+	  objectLink: React.PropTypes.func
+	};
+	
+	function ObjectWithURL(props) {
+	  let grip = props.object;
+	  return span({ className: "objectBox objectBox-" + getType(grip) }, getTitle(props, grip), span({ className: "objectPropValue" }, getDescription(grip)));
+	}
+	
+	function getTitle(props, grip) {
+	  return safeObjectLink(props, { className: "objectBox" }, getType(grip) + " ");
+	}
+	
+	function getType(grip) {
+	  return grip.class;
+	}
+	
+	function getDescription(grip) {
+	  return getURLDisplayString(grip.preview.url);
+	}
 	
 	// Registration
-	
 	function supportsObject(grip, type) {
 	  if (!isGrip(grip)) {
 	    return false;
 	  }
 	
 	  return grip.preview && grip.preview.kind == "ObjectWithURL";
 	}
 	
 	// Exports from this module
 	module.exports = {
-	  rep: ObjectWithURL,
-	  supportsObject: supportsObject
+	  rep: wrapRender(ObjectWithURL),
+	  supportsObject
 	};
 
 /***/ },
 /* 37 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
-	const React = __webpack_require__(3);
+	const React = __webpack_require__(4);
 	const {
-	  createFactories,
 	  isGrip,
+	  safeObjectLink,
 	  wrapRender
-	} = __webpack_require__(4);
-	const Caption = React.createFactory(__webpack_require__(12));
+	} = __webpack_require__(3);
+	const Caption = __webpack_require__(12);
 	const { MODE } = __webpack_require__(1);
 	
 	// Shortcuts
 	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",
-	
-	  propTypes: {
-	    object: React.PropTypes.object.isRequired,
-	    // @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,
-	    objectLink: React.PropTypes.func,
-	    attachedActorIds: React.PropTypes.array,
-	    onDOMNodeMouseOver: React.PropTypes.func,
-	    onDOMNodeMouseOut: React.PropTypes.func,
-	    onInspectIconClick: React.PropTypes.func
-	  },
-	
-	  getLength: function (grip) {
-	    if (!grip.preview) {
-	      return 0;
-	    }
-	
-	    return grip.preview.length || grip.preview.childNodesLength || 0;
-	  },
-	
-	  getTitle: function (object, context) {
-	    if (this.props.mode === MODE.TINY) {
-	      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;
-	  },
-	
-	  arrayIterator: function (grip, max) {
-	    let items = [];
-	    const gripLength = this.getLength(grip);
-	
-	    if (!gripLength) {
-	      return items;
-	    }
-	
-	    const previewItems = this.getPreviewItems(grip);
-	    if (!previewItems) {
-	      return items;
-	    }
-	
-	    let delim;
-	    // number of grip preview items is limited to 10, but we may have more
-	    // items in grip-array.
-	    let delimMax = gripLength > previewItems.length ? previewItems.length : previewItems.length - 1;
-	    let provider = this.props.provider;
-	
-	    for (let i = 0; i < previewItems.length && i < max; i++) {
-	      try {
-	        let itemGrip = previewItems[i];
-	        let value = provider ? provider.getValue(itemGrip) : itemGrip;
-	
-	        delim = i == delimMax ? "" : ", ";
-	
-	        items.push(GripArrayItem(Object.assign({}, this.props, {
-	          object: value,
-	          delim: delim,
-	          // Do not propagate title to array items reps
-	          title: undefined
-	        })));
-	      } catch (exc) {
-	        items.push(GripArrayItem(Object.assign({}, this.props, {
-	          object: exc,
-	          delim: delim,
-	          // Do not propagate title to array items reps
-	          title: undefined
-	        })));
-	      }
-	    }
-	    if (previewItems.length > max || gripLength > previewItems.length) {
-	      let leftItemNum = gripLength - max > 0 ? gripLength - max : gripLength - previewItems.length;
-	      items.push(Caption({
-	        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;
-	    let needSpace = function (space) {
-	      return space ? { left: "[ ", right: " ]" } : { left: "[", right: "]" };
-	    };
-	
-	    if (mode === MODE.TINY) {
-	      let objectLength = this.getLength(object);
-	      let isEmpty = objectLength === 0;
-	      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 title = this.getTitle(object);
-	
-	    return span({
-	      className: "objectBox objectBox-array" }, title, this.safeObjectLink({
-	      className: "arrayLeftBracket"
-	    }, brackets.left), ...items, this.safeObjectLink({
-	      className: "arrayRightBracket"
-	    }, brackets.right), span({
-	      className: "arrayProperties",
-	      role: "group" }));
-	  })
-	});
+	GripArray.propTypes = {
+	  object: React.PropTypes.object.isRequired,
+	  // @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,
+	  objectLink: React.PropTypes.func,
+	  onDOMNodeMouseOver: React.PropTypes.func,
+	  onDOMNodeMouseOut: React.PropTypes.func,
+	  onInspectIconClick: React.PropTypes.func
+	};
+	
+	function GripArray(props) {
+	  let {
+	    object,
+	    mode = MODE.SHORT
+	  } = props;
+	
+	  let items;
+	  let brackets;
+	  let needSpace = function (space) {
+	    return space ? { left: "[ ", right: " ]" } : { left: "[", right: "]" };
+	  };
+	
+	  if (mode === MODE.TINY) {
+	    let objectLength = getLength(object);
+	    let isEmpty = objectLength === 0;
+	    items = [span({ className: "length" }, isEmpty ? "" : objectLength)];
+	    brackets = needSpace(false);
+	  } else {
+	    let max = mode === MODE.SHORT ? 3 : 10;
+	    items = arrayIterator(props, object, max);
+	    brackets = needSpace(items.length > 0);
+	  }
+	
+	  let title = getTitle(props, object);
+	
+	  return span({
+	    className: "objectBox objectBox-array" }, title, safeObjectLink(props, {
+	    className: "arrayLeftBracket"
+	  }, brackets.left), ...items, safeObjectLink(props, {
+	    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.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
-	  },
-	
-	  render: function () {
-	    let { Rep } = createFactories(__webpack_require__(2));
-	
-	    return span({}, Rep(Object.assign({}, this.props, {
-	      mode: MODE.TINY
-	    })), this.props.delim);
+	GripArrayItem.propTypes = {
+	  delim: React.PropTypes.string,
+	  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,
+	  onDOMNodeMouseOver: React.PropTypes.func,
+	  onDOMNodeMouseOut: React.PropTypes.func,
+	  onInspectIconClick: React.PropTypes.func
+	};
+	
+	function GripArrayItem(props) {
+	  let { Rep } = __webpack_require__(2);
+	  let {
+	    delim
+	  } = props;
+	
+	  return span({}, Rep(Object.assign({}, props, {
+	    mode: MODE.TINY
+	  })), delim);
+	}
+	
+	function getLength(grip) {
+	  if (!grip.preview) {
+	    return 0;
+	  }
+	
+	  return grip.preview.length || grip.preview.childNodesLength || 0;
+	}
+	
+	function getTitle(props, object, context) {
+	  if (props.mode === MODE.TINY) {
+	    return "";
+	  }
+	
+	  let title = props.title || object.class || "Array";
+	  return safeObjectLink(props, {}, title + " ");
+	}
+	
+	function getPreviewItems(grip) {
+	  if (!grip.preview) {
+	    return null;
 	  }
-	}));
+	
+	  return grip.preview.items || grip.preview.childNodes || null;
+	}
+	
+	function arrayIterator(props, grip, max) {
+	  let items = [];
+	  const gripLength = getLength(grip);
+	
+	  if (!gripLength) {
+	    return items;
+	  }
+	
+	  const previewItems = getPreviewItems(grip);
+	  if (!previewItems) {
+	    return items;
+	  }
+	
+	  let delim;
+	  // number of grip preview items is limited to 10, but we may have more
+	  // items in grip-array.
+	  let delimMax = gripLength > previewItems.length ? previewItems.length : previewItems.length - 1;
+	  let provider = props.provider;
+	
+	  for (let i = 0; i < previewItems.length && i < max; i++) {
+	    try {
+	      let itemGrip = previewItems[i];
+	      let value = provider ? provider.getValue(itemGrip) : itemGrip;
+	
+	      delim = i == delimMax ? "" : ", ";
+	
+	      items.push(GripArrayItem(Object.assign({}, props, {
+	        object: value,
+	        delim: delim,
+	        // Do not propagate title to array items reps
+	        title: undefined
+	      })));
+	    } catch (exc) {
+	      items.push(GripArrayItem(Object.assign({}, props, {
+	        object: exc,
+	        delim: delim,
+	        // Do not propagate title to array items reps
+	        title: undefined
+	      })));
+	    }
+	  }
+	  if (previewItems.length > max || gripLength > previewItems.length) {
+	    let leftItemNum = gripLength - max > 0 ? gripLength - max : gripLength - previewItems.length;
+	    items.push(Caption({
+	      object: safeObjectLink(props, {}, leftItemNum + " more…")
+	    }));
+	  }
+	
+	  return items;
+	}
 	
 	function supportsObject(grip, type) {
 	  if (!isGrip(grip)) {
 	    return false;
 	  }
 	
 	  return grip.preview && (grip.preview.kind == "ArrayLike" || type === "DocumentFragment");
 	}
 	
 	// Exports from this module
 	module.exports = {
-	  rep: GripArray,
-	  supportsObject: supportsObject
+	  rep: wrapRender(GripArray),
+	  supportsObject
 	};
 
 /***/ },
 /* 38 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
-	const React = __webpack_require__(3);
+	const React = __webpack_require__(4);
 	const {
 	  isGrip,
+	  safeObjectLink,
 	  wrapRender
-	} = __webpack_require__(4);
-	const Caption = React.createFactory(__webpack_require__(12));
-	const PropRep = React.createFactory(__webpack_require__(14));
+	} = __webpack_require__(3);
+	const Caption = __webpack_require__(12);
+	const PropRep = __webpack_require__(14);
 	const { MODE } = __webpack_require__(1);
 	// Shortcuts
 	const { span } = React.DOM;
+	
 	/**
 	 * Renders an map. A map is represented by a list of its
 	 * entries enclosed in curly brackets.
 	 */
-	const GripMap = React.createClass({
-	  displayName: "GripMap",
-	
-	  propTypes: {
-	    object: React.PropTypes.object,
-	    // @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,
-	    isInterestingEntry: React.PropTypes.func,
-	    attachedActorIds: React.PropTypes.array,
-	    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");
-	    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);
-	    }
-	    return [];
-	  },
-	
-	  entriesIterator: function (object, max) {
-	    // Entry filter. Show only interesting entries to the user.
-	    let isInterestingEntry = this.props.isInterestingEntry || ((type, value) => {
-	      return type == "boolean" || type == "number" || type == "string" && value.length != 0;
-	    });
-	
-	    let mapEntries = object.preview && object.preview.entries ? object.preview.entries : [];
-	
-	    let indexes = this.getEntriesIndexes(mapEntries, max, isInterestingEntry);
-	    if (indexes.length < max && indexes.length < mapEntries.length) {
-	      // There are not enough entries yet, so we add uninteresting entries.
-	      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…".
-	      entries.push(Caption({
-	        key: "more",
-	        object: this.safeObjectLink({}, `${mapEntries.length - max} more…`)
-	      }));
-	    }
-	
-	    return entries;
-	  },
-	
-	  /**
-	   * Get entries ordered by index.
-	   *
-	   * @param {Array} entries Entries array.
-	   * @param {Array} indexes Indexes of entries.
-	   * @return {Array} Array of PropRep.
-	   */
-	  getEntries: function (entries, indexes) {
-	    let {
+	GripMap.propTypes = {
+	  object: React.PropTypes.object,
+	  // @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,
+	  isInterestingEntry: React.PropTypes.func,
+	  onDOMNodeMouseOver: React.PropTypes.func,
+	  onDOMNodeMouseOut: React.PropTypes.func,
+	  onInspectIconClick: React.PropTypes.func,
+	  title: React.PropTypes.string
+	};
+	
+	function GripMap(props) {
+	  let object = props.object;
+	  let propsArray = safeEntriesIterator(props, object, props.mode === MODE.LONG ? 10 : 3);
+	
+	  if (props.mode === MODE.TINY) {
+	    return span({ className: "objectBox objectBox-object" }, getTitle(props, object));
+	  }
+	
+	  return span({ className: "objectBox objectBox-object" }, getTitle(props, object), safeObjectLink(props, {
+	    className: "objectLeftBrace"
+	  }, " { "), propsArray, safeObjectLink(props, {
+	    className: "objectRightBrace"
+	  }, " }"));
+	}
+	
+	function getTitle(props, object) {
+	  let title = props.title || (object && object.class ? object.class : "Map");
+	  return safeObjectLink(props, {}, title);
+	}
+	
+	function safeEntriesIterator(props, object, max) {
+	  max = typeof max === "undefined" ? 3 : max;
+	  try {
+	    return entriesIterator(props, object, max);
+	  } catch (err) {
+	    console.error(err);
+	  }
+	  return [];
+	}
+	
+	function entriesIterator(props, object, max) {
+	  // Entry filter. Show only interesting entries to the user.
+	  let isInterestingEntry = props.isInterestingEntry || ((type, value) => {
+	    return type == "boolean" || type == "number" || type == "string" && value.length != 0;
+	  });
+	
+	  let mapEntries = object.preview && object.preview.entries ? object.preview.entries : [];
+	
+	  let indexes = getEntriesIndexes(mapEntries, max, isInterestingEntry);
+	  if (indexes.length < max && indexes.length < mapEntries.length) {
+	    // There are not enough entries yet, so we add uninteresting entries.
+	    indexes = indexes.concat(getEntriesIndexes(mapEntries, max - indexes.length, (t, value, name) => {
+	      return !isInterestingEntry(t, value, name);
+	    }));
+	  }
+	
+	  let entries = getEntries(props, mapEntries, indexes);
+	  if (entries.length < mapEntries.length) {
+	    // There are some undisplayed entries. Then display "more…".
+	    entries.push(Caption({
+	      key: "more",
+	      object: safeObjectLink(props, {}, `${mapEntries.length - max} more…`)
+	    }));
+	  }
+	
+	  return entries;
+	}
+	
+	/**
+	 * Get entries ordered by index.
+	 *
+	 * @param {Object} props Component props.
+	 * @param {Array} entries Entries array.
+	 * @param {Array} indexes Indexes of entries.
+	 * @return {Array} Array of PropRep.
+	 */
+	function getEntries(props, entries, indexes) {
+	  let {
+	    objectLink,
+	    onDOMNodeMouseOver,
+	    onDOMNodeMouseOut,
+	    onInspectIconClick
+	  } = props;
+	
+	  // Make indexes ordered by ascending.
+	  indexes.sort(function (a, b) {
+	    return a - b;
+	  });
+	
+	  return indexes.map((index, i) => {
+	    let [key, entryValue] = entries[index];
+	    let value = entryValue.value !== undefined ? entryValue.value : entryValue;
+	
+	    return PropRep({
+	      // key,
+	      name: key,
+	      equal: ": ",
+	      object: value,
+	      // Do not add a trailing comma on the last entry
+	      // if there won't be a "more..." item.
+	      delim: i < indexes.length - 1 || indexes.length < entries.length ? ", " : null,
+	      mode: MODE.TINY,
 	      objectLink,
-	      attachedActorIds,
 	      onDOMNodeMouseOver,
 	      onDOMNodeMouseOut,
 	      onInspectIconClick
-	    } = this.props;
-	
-	    // Make indexes ordered by ascending.
-	    indexes.sort(function (a, b) {
-	      return a - b;
 	    });
-	
-	    return indexes.map((index, i) => {
-	      let [key, entryValue] = entries[index];
-	      let value = entryValue.value !== undefined ? entryValue.value : entryValue;
-	
-	      return PropRep({
-	        // key,
-	        name: key,
-	        equal: ": ",
-	        object: value,
-	        // Do not add a trailing comma on the last entry
-	        // if there won't be a "more..." item.
-	        delim: i < indexes.length - 1 || indexes.length < entries.length ? ", " : "",
-	        mode: MODE.TINY,
-	        objectLink,
-	        attachedActorIds,
-	        onDOMNodeMouseOver,
-	        onDOMNodeMouseOut,
-	        onInspectIconClick
-	      });
-	    });
-	  },
-	
-	  /**
-	   * Get the indexes of entries in the map.
-	   *
-	   * @param {Array} entries Entries array.
-	   * @param {Number} max The maximum length of indexes array.
-	   * @param {Function} filter Filter the entry you want.
-	   * @return {Array} Indexes of filtered entries in the map.
-	   */
-	  getEntriesIndexes: function (entries, max, filter) {
-	    return entries.reduce((indexes, [key, entry], i) => {
-	      if (indexes.length < max) {
-	        let value = entry && entry.value !== undefined ? entry.value : entry;
-	        // Type is specified in grip's "class" field and for primitive
-	        // values use typeof.
-	        let type = (value && value.class ? value.class : typeof value).toLowerCase();
-	
-	        if (filter(type, value, key)) {
-	          indexes.push(i);
-	        }
+	  });
+	}
+	
+	/**
+	 * Get the indexes of entries in the map.
+	 *
+	 * @param {Array} entries Entries array.
+	 * @param {Number} max The maximum length of indexes array.
+	 * @param {Function} filter Filter the entry you want.
+	 * @return {Array} Indexes of filtered entries in the map.
+	 */
+	function getEntriesIndexes(entries, max, filter) {
+	  return entries.reduce((indexes, [key, entry], i) => {
+	    if (indexes.length < max) {
+	      let value = entry && entry.value !== undefined ? entry.value : entry;
+	      // Type is specified in grip's "class" field and for primitive
+	      // values use typeof.
+	      let type = (value && value.class ? value.class : typeof value).toLowerCase();
+	
+	      if (filter(type, value, key)) {
+	        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);
-	
-	    if (this.props.mode === MODE.TINY) {
-	      return span({ className: "objectBox objectBox-object" }, this.getTitle(object));
-	    }
-	
-	    return span({ className: "objectBox objectBox-object" }, this.getTitle(object), this.safeObjectLink({
-	      className: "objectLeftBrace"
-	    }, " { "), propsArray, this.safeObjectLink({
-	      className: "objectRightBrace"
-	    }, " }"));
-	  })
-	});
+	    return indexes;
+	  }, []);
+	}
 	
 	function supportsObject(grip, type) {
 	  if (!isGrip(grip)) {
 	    return false;
 	  }
 	  return grip.preview && grip.preview.kind == "MapLike";
 	}
 	
 	// Exports from this module
 	module.exports = {
-	  rep: GripMap,
-	  supportsObject: supportsObject
+	  rep: wrapRender(GripMap),
+	  supportsObject
 	};
 
 /***/ }
 /******/ ])
 });
 ;
 //# sourceMappingURL=reps.js.map
\ No newline at end of file
--- a/devtools/client/shared/components/reps/test/mochitest/head.js
+++ b/devtools/client/shared/components/reps/test/mochitest/head.js
@@ -64,12 +64,8 @@ function testRepRenderModes(modeTests, t
 
     const rendered = renderComponent(
       componentUnderTest.rep,
       Object.assign({}, { object: gripStub, mode, title }, props)
     );
     is(rendered.textContent, expectedOutput, message);
   });
 }
-
-function getStubAttachedActorIds(gripStubs) {
-  return gripStubs.map(gripStub => gripStub.actor);
-}
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_array.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_array.html
@@ -15,18 +15,22 @@ Test ArrayRep rep
 <body>
 <pre id="test">
 <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;
+  const {
+    REPS,
+    MODE,
+    getRep,
+  } = browserRequire("devtools/client/shared/components/reps/reps");
+  let { ArrayRep } = REPS;
 
   let componentUnderTest = ArrayRep;
   const maxLength = {
     short: 3,
     long: 10
   };
 
   try {
@@ -47,19 +51,17 @@ window.onload = Task.async(function* () 
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
   function testBasic() {
     // Test that correct rep is chosen
     const stub = [];
-    const renderedRep = shallowRenderComponent(Rep, { object: stub });
-    is(renderedRep.type, ArrayRep.rep,
-       `Rep correctly selects ${ArrayRep.rep.displayName}`);
+    is(getRep(stub), ArrayRep.rep, "Rep correctly selects Array Rep");
 
 
     // Test rendering
     const defaultOutput = `[]`;
 
     const modeTests = [
       {
         mode: undefined,
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_attribute.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_attribute.html
@@ -12,32 +12,34 @@ Test Attribute rep
   <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"></script>
 <script type="application/javascript">
 window.onload = Task.async(function* () {
-  const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
-  let { Rep, Attribute } = REPS;
+  const {
+    REPS,
+    getRep,
+  } = browserRequire("devtools/client/shared/components/reps/reps");
+  let { Attribute } = REPS;
 
   try {
     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}`);
+    is(getRep(getStub()), Attribute.rep, "Rep correctly selects Attribute Rep");
 
     // 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, {
--- 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
@@ -15,18 +15,22 @@ Test comment-node rep
 <body>
 <pre id="test">
 <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;
+    const {
+      REPS,
+      MODE,
+      getRep,
+    } = browserRequire("devtools/client/shared/components/reps/reps");
+    let { CommentNode } = REPS;
 
     let gripStub = {
       "type": "object",
       "actor": "server1.conn1.child1/obj47",
       "class": "Comment",
       "extensible": true,
       "frozen": false,
       "sealed": false,
@@ -35,19 +39,17 @@ window.onload = Task.async(function* () 
         "kind": "DOMNode",
         "nodeType": 8,
         "nodeName": "#comment",
         "textContent": "test\nand test\nand test\nand test\nand test\nand test\nand test"
       }
     };
 
     // Test that correct rep is chosen.
-    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
-    is(renderedRep.type, CommentNode.rep,
-      `Rep correctly selects ${CommentNode.rep.displayName}`);
+    is(getRep(gripStub), CommentNode.rep, "Rep correctly selects CommentNode Rep");
 
     // Test rendering.
     const renderedComponent = renderComponent(CommentNode.rep, { object: gripStub });
     is(renderedComponent.className, "objectBox theme-comment",
       "CommentNode rep has expected class names");
     is(renderedComponent.textContent,
       `<!-- test\nand test\nand test\nan…d test\nand test\nand test -->`,
       "CommentNode rep has expected text content");
--- 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
@@ -12,18 +12,21 @@ Test DateTime rep
   <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"></script>
 <script type="application/javascript">
 window.onload = Task.async(function* () {
-  const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
-  let { Rep, DateTime } = REPS;
+  const {
+    REPS,
+    getRep,
+  } = browserRequire("devtools/client/shared/components/reps/reps");
+  let { DateTime } = REPS;
 
   try {
     testValid();
     testInvalid();
     testObjectLink();
   } catch(e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
@@ -40,22 +43,21 @@ window.onload = Task.async(function* () 
       "sealed": false,
       "ownPropertyLength": 0,
       "preview": {
         "timestamp": 1459372644859
       }
     };
 
     // Test that correct rep is chosen
-    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
-    is(renderedRep.type, DateTime.rep, `Rep correctly selects ${DateTime.rep.displayName}`);
+    is(getRep(gripStub), DateTime.rep, "Rep correctly selects DateTime Rep");
 
     // Test rendering
     const renderedComponent = renderComponent(DateTime.rep, { object: gripStub });
-    is(renderedComponent.textContent, "2016-03-30T21:17:24.859Z", "DateTime rep has expected text content for valid date");
+    is(renderedComponent.textContent, "Date 2016-03-30T21:17:24.859Z", "DateTime rep has expected text content for valid date");
   }
 
   function testInvalid() {
     let gripStub = {
       "type": "object",
       "actor": "server1.conn0.child1/obj32",
       "class": "Date",
       "extensible": true,
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_document.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_document.html
@@ -12,36 +12,38 @@ Test Document rep
   <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"></script>
 <script type="application/javascript">
 window.onload = Task.async(function* () {
-  const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
-  let { Rep, Document } = REPS;
+  const {
+    REPS,
+    getRep,
+  } = browserRequire("devtools/client/shared/components/reps/reps");
+  let { Document } = REPS;
 
   try {
     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}`);
+    is(getRep(getStub()), Document.rep, "Rep correctly selects Document Rep");
 
     // Test rendering
     const renderedComponent = renderComponent(Document.rep, { object: getStub() });
-    is(renderedComponent.textContent, "https://www.mozilla.org/en-US/firefox/new/",
+    is(renderedComponent.textContent, "HTMLDocument 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({},
--- 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
@@ -17,19 +17,20 @@ Test Element node rep
 <script src="head.js" type="application/javascript"></script>
 <script type="application/javascript">
 "use strict";
 
 window.onload = Task.async(function* () {
   const {
     REPS,
     MODE,
+    getRep,
     getSelectableInInspectorGrips,
   } = browserRequire("devtools/client/shared/components/reps/reps");
-  let { Rep, ElementNode } = REPS;
+  let { ElementNode } = REPS;
 
   try {
     yield testBodyNode();
     yield testDocumentElement();
     yield testNode();
     yield testNodeWithLeadingAndTrailingSpacesClassName();
     yield testNodeWithoutAttributes();
     yield testLotsOfAttributes();
@@ -43,70 +44,66 @@ window.onload = Task.async(function* () 
   } catch (e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
   function testBodyNode() {
     const stub = getGripStub("testBodyNode");
-    const renderedRep = shallowRenderComponent(Rep, { object: stub });
-    is(renderedRep.type, ElementNode.rep,
-      `Rep correctly selects ${ElementNode.rep.displayName} for body node`);
+    is(getRep(stub), ElementNode.rep,
+      "Rep correctly selects ElementNode Rep for body node");
 
     const renderedComponent = renderComponent(ElementNode.rep, { object: stub });
     is(renderedComponent.textContent, `<body id="body-id" class="body-class">`,
       "Element node rep has expected text content for body node");
 
     const tinyRenderedComponent = renderComponent(
       ElementNode.rep, { object: stub, mode: MODE.TINY });
     is(tinyRenderedComponent.textContent, `body#body-id.body-class`,
       "Element node rep has expected text content for body node in tiny mode");
   }
 
   function testDocumentElement() {
     const stub = getGripStub("testDocumentElement");
-    const renderedRep = shallowRenderComponent(Rep, { object: stub });
-    is(renderedRep.type, ElementNode.rep,
-      `Rep correctly selects ${ElementNode.rep.displayName} for document element node`);
+    is(getRep(stub), ElementNode.rep,
+      "Rep correctly selects ElementNode Rep for document element node");
 
     const renderedComponent = renderComponent(ElementNode.rep, { object: stub });
     is(renderedComponent.textContent, `<html dir="ltr" lang="en-US">`,
       "Element node rep has expected text content for document element node");
 
     const tinyRenderedComponent = renderComponent(
       ElementNode.rep, { object: stub, mode: MODE.TINY });
     is(tinyRenderedComponent.textContent, `html`,
       "Element node rep has expected text content for document element in tiny mode");
   }
 
   function testNode() {
     const stub = getGripStub("testNode");
-    const renderedRep = shallowRenderComponent(Rep, { object: stub });
-    is(renderedRep.type, ElementNode.rep,
-      `Rep correctly selects ${ElementNode.rep.displayName} for element node`);
+    is(getRep(stub), ElementNode.rep,
+      "Rep correctly selects ElementNode Rep for element node");
 
     const renderedComponent = renderComponent(ElementNode.rep, { object: stub });
     is(renderedComponent.textContent,
       `<input id="newtab-customize-button" class="bar baz" dir="ltr" ` +
       `title="Customize your New Tab page" value="foo" type="button">`,
       "Element node rep has expected text content for element node");
 
     const tinyRenderedComponent = renderComponent(
       ElementNode.rep, { object: stub, mode: MODE.TINY });
     is(tinyRenderedComponent.textContent,
       `input#newtab-customize-button.bar.baz`,
       "Element node rep has expected text content for element node in tiny mode");
   }
 
   function testNodeWithLeadingAndTrailingSpacesClassName() {
     const stub = getGripStub("testNodeWithLeadingAndTrailingSpacesClassName");
-    const renderedRep = shallowRenderComponent(Rep, { object: stub });
-    is(renderedRep.type, ElementNode.rep,
-      `Rep correctly selects ${ElementNode.rep.displayName} for element node`);
+    is(getRep(stub), ElementNode.rep,
+      "Rep correctly selects ElementNode Rep for element node");
 
     const renderedComponent = renderComponent(ElementNode.rep, { object: stub });
     is(renderedComponent.textContent,
       `<body id="nightly-whatsnew" class="  html-ltr    ">`,
       "Element node rep output element node with the class trailing spaces");
 
     const tinyRenderedComponent = renderComponent(
       ElementNode.rep, { object: stub, mode: MODE.TINY });
@@ -114,18 +111,17 @@ window.onload = Task.async(function* () 
       `body#nightly-whatsnew.html-ltr`,
       "Element node rep does not show leading nor trailing spaces " +
       "on class attribute in tiny mode");
   }
 
   function testNodeWithoutAttributes() {
     const stub = getGripStub("testNodeWithoutAttributes");
 
-    const renderedComponent = renderComponent(ElementNode.rep, { object: stub });
-    is(renderedComponent.textContent, "<p>",
+    is(getRep(stub), ElementNode.rep,
       "Element node rep has expected text content for element node without attributes");
 
     const tinyRenderedComponent = renderComponent(
       ElementNode.rep, { object: stub, mode: MODE.TINY });
     is(tinyRenderedComponent.textContent, `p`,
       "Element node rep has expected text content for element node without attributes");
   }
 
@@ -142,133 +138,112 @@ window.onload = Task.async(function* () 
       ElementNode.rep, { object: stub, mode: MODE.TINY });
     is(tinyRenderedComponent.textContent, `p#lots-of-attributes`,
       "Element node rep has expected text content for node in tiny mode");
   }
 
   function testSvgNode() {
     const stub = getGripStub("testSvgNode");
 
-    const renderedRep = shallowRenderComponent(Rep, { object: stub });
-    is(renderedRep.type, ElementNode.rep,
+    is(getRep(stub), ElementNode.rep,
       `Rep correctly selects ${ElementNode.rep.displayName} for SVG element node`);
 
     const renderedComponent = renderComponent(ElementNode.rep, { object: stub });
     is(renderedComponent.textContent,
       '<clipPath id="clip" class="svg-element">',
       "Element node rep has expected text content for SVG element node");
 
     const tinyRenderedComponent = renderComponent(
       ElementNode.rep, { object: stub, mode: MODE.TINY });
     is(tinyRenderedComponent.textContent, `clipPath#clip.svg-element`,
       "Element node rep has expected text content for SVG element node in tiny mode");
   }
 
   function testSvgNodeInXHTML() {
     const stub = getGripStub("testSvgNodeInXHTML");
 
-    const renderedRep = shallowRenderComponent(Rep, { object: stub });
-    is(renderedRep.type, ElementNode.rep,
+    is(getRep(stub), ElementNode.rep,
       `Rep correctly selects ${ElementNode.rep.displayName} for XHTML SVG element node`);
 
     const renderedComponent = renderComponent(ElementNode.rep, { object: stub });
     is(renderedComponent.textContent,
       '<svg:circle class="svg-element" cx="0" cy="0" r="5">',
       "Element node rep has expected text content for XHTML SVG element node");
 
     const tinyRenderedComponent = renderComponent(
       ElementNode.rep, { object: stub, mode: MODE.TINY });
     is(tinyRenderedComponent.textContent, `svg:circle.svg-element`,
       "Element node rep has expected text content for XHTML SVG element in tiny mode");
   }
 
   function testOnMouseOver() {
     const stub = getGripStub("testNode");
-  debugger;
     const grips = getSelectableInInspectorGrips(stub);
     is(grips.length, 1, "the stub has one node grip");
 
-    const attachedActorIds = getStubAttachedActorIds(grips);
-
     let mouseOverValue;
     let onDOMNodeMouseOver = (object) => {
       mouseOverValue = object;
     };
     const renderedComponent = renderComponent(ElementNode.rep, {
       object: stub,
       onDOMNodeMouseOver,
-      attachedActorIds,
     });
 
     TestUtils.Simulate.mouseOver(renderedComponent);
 
     is(mouseOverValue, grips[0], "onDOMNodeMouseOver is called " +
       "with the expected argument when mouseover is fired on the Rep");
   }
 
   function testOnMouseOut() {
     const stub = getGripStub("testNode");
     const grips = getSelectableInInspectorGrips(stub);
     is(grips.length, 1, "the stub has one node grip");
 
-    const attachedActorIds = getStubAttachedActorIds(grips);
-
     let called = false;
     let onDOMNodeMouseOut = (object) => {
       called = true;
     };
     const renderedComponent = renderComponent(ElementNode.rep, {
       object: stub,
       onDOMNodeMouseOut,
-      attachedActorIds,
     });
 
     TestUtils.Simulate.mouseOut(renderedComponent);
 
     is(called, true, "onDOMNodeMouseOut is called when mouseout is fired on the Rep");
   }
 
   function testOnInspectIconClick() {
     const stub = getGripStub("testNode");
     const grips = getSelectableInInspectorGrips(stub);
     is(grips.length, 1, "the stub has one node grip");
 
-    const attachedActorIds = getStubAttachedActorIds(grips);
-
     let inspectIconClickedValue = null;
     let inspectIconClickedEvent = null;
 
     let onInspectIconClick = (object, event) => {
       inspectIconClickedValue = object;
       inspectIconClickedEvent = event;
     };
 
     const renderedComponentWithoutInspectIcon = renderComponent(ElementNode.rep, {
-      object: stub,
+      object: getGripStub("testDisconnectedNode"),
       onInspectIconClick,
-      attachedActorIds: ["someOtherId"]
     });
     is(renderedComponentWithoutInspectIcon.querySelector(".open-inspector"), null,
-      "There isn't an inspect icon when actor is not in attachedActorIds");
+      "There isn't an inspect icon when the node is not in the DOM tree");
 
-    let renderedComponent = renderComponent(ElementNode.rep, {
+    const renderedComponent = renderComponent(ElementNode.rep, {
       object: stub,
       onInspectIconClick,
     });
-    let inspectIconNode = renderedComponent.querySelector(".open-inspector");
-    ok(inspectIconNode !== null,
-      "There is an inspect icon when attachedActorIds is not specified");
 
-    renderedComponent = renderComponent(ElementNode.rep, {
-      object: stub,
-      onInspectIconClick,
-      attachedActorIds,
-    });
-
-    inspectIconNode = renderedComponent.querySelector(".open-inspector");
+    const inspectIconNode = renderedComponent.querySelector(".open-inspector");
     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");
   }
@@ -340,16 +315,42 @@ window.onload = Task.async(function* () 
           "extensible": true,
           "frozen": false,
           "sealed": false,
           "ownPropertyLength": 0,
           "preview": {
             "kind": "DOMNode",
             "nodeType": 1,
             "nodeName": "input",
+            "isConnected": true,
+            "attributes": {
+              "id": "newtab-customize-button",
+              "dir": "ltr",
+              "title": "Customize your New Tab page",
+              "class": "bar baz",
+              "value": "foo",
+              "type": "button"
+            },
+            "attributesLength": 6
+          }
+        };
+      case "testDisconnectedNode":
+        return {
+          "type": "object",
+          "actor": "server1.conn2.child1/obj116",
+          "class": "HTMLInputElement",
+          "extensible": true,
+          "frozen": false,
+          "sealed": false,
+          "ownPropertyLength": 0,
+          "preview": {
+            "kind": "DOMNode",
+            "nodeType": 1,
+            "nodeName": "input",
+            "isConnected": false,
             "attributes": {
               "id": "newtab-customize-button",
               "dir": "ltr",
               "title": "Customize your New Tab page",
               "class": "bar baz",
               "value": "foo",
               "type": "button"
             },
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_error.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_error.html
@@ -14,18 +14,22 @@ Test Error rep
 </head>
 <body>
 <pre id="test">
 <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;
+  const {
+    REPS,
+    MODE,
+    getRep,
+  } = browserRequire("devtools/client/shared/components/reps/reps");
+  let { ErrorRep } = REPS;
 
   try {
     // Test errors with different properties
     yield testSimpleError();
     yield testMultilineStackError();
     yield testErrorWithoutStacktrace();
 
     // Test different kind of errors
@@ -42,19 +46,17 @@ window.onload = Task.async(function* () 
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
   function testSimpleError() {
     // Test object = `new Error("Error message")`
     const stub = getGripStub("testSimpleError");
-    const renderedRep = shallowRenderComponent(Rep, {object: stub});
-    is(renderedRep.type, ErrorRep.rep,
-      `Rep correctly selects ${ErrorRep.rep.displayName} for Error object`);
+    is(getRep(stub), ErrorRep.rep, "Rep correctly selects Error Rep for Error object");
 
     const renderedComponent = renderComponent(ErrorRep.rep, {object: stub});
     is(renderedComponent.textContent,
       "Error: Error message\n" +
       "Stack trace:\n" +
       "@debugger eval code:1:13\n",
       "Error Rep has expected text content for a simple error");
 
@@ -72,19 +74,18 @@ window.onload = Task.async(function* () 
      *     errorBar();
      *   }
      *   function errorBar() {
      *     console.log(new Error("bar"));
      *   }
      *   errorFoo();`
      */
     const stub = getGripStub("testMultilineStackError");
-    const renderedRep = shallowRenderComponent(Rep, {object: stub});
-    is(renderedRep.type, ErrorRep.rep,
-      `Rep correctly selects ${ErrorRep.rep.displayName} for Error object`);
+    is(getRep(stub), ErrorRep.rep,
+      `Rep correctly selects Error Rep for Error object`);
 
     const renderedComponent = renderComponent(ErrorRep.rep, {object: stub});
     is(renderedComponent.textContent,
       "Error: bar\n" +
       "Stack trace:\n" +
       "errorBar@debugger eval code:6:15\n" +
       "errorFoo@debugger eval code:3:3\n" +
       "@debugger eval code:8:1\n",
@@ -94,38 +95,36 @@ window.onload = Task.async(function* () 
       ErrorRep.rep, {object: stub, mode: MODE.TINY});
     is(tinyRenderedComponent.textContent,
       "Error",
       "Error Rep has expected text content for an error with a multiple line in tiny mode");
   }
 
   function testErrorWithoutStacktrace() {
     const stub = getGripStub("testErrorWithoutStacktrace");
-    const renderedRep = shallowRenderComponent(Rep, {object: stub});
-    is(renderedRep.type, ErrorRep.rep,
-      `Rep correctly selects ${ErrorRep.rep.displayName} for Error object`);
+    is(getRep(stub), ErrorRep.rep,
+      `Rep correctly selects Error Rep for Error object`);
 
     const renderedComponent = renderComponent(ErrorRep.rep, {object: stub});
     is(renderedComponent.textContent,
       "Error: Error message",
       "Error Rep has expected text content for an error without stacktrace");
 
     const tinyRenderedComponent = renderComponent(
       ErrorRep.rep, {object: stub, mode: MODE.TINY});
     is(tinyRenderedComponent.textContent,
       "Error",
       "Error Rep has expected text content for an error without stacktrace in tiny mode");
   }
 
   function testEvalError() {
     // Test object = `new EvalError("EvalError message")`
     const stub = getGripStub("testEvalError");
-    const renderedRep = shallowRenderComponent(Rep, {object: stub});
-    is(renderedRep.type, ErrorRep.rep,
-      `Rep correctly selects ${ErrorRep.rep.displayName} for EvalError object`);
+    is(getRep(stub), ErrorRep.rep,
+      `Rep correctly selects Error Rep for EvalError object`);
 
     const renderedComponent = renderComponent(ErrorRep.rep, {object: stub});
     is(renderedComponent.textContent,
       "EvalError: EvalError message\n" +
       "Stack trace:\n" +
       "@debugger eval code:10:13\n",
       "Error Rep has expected text content for an EvalError");
 
@@ -134,19 +133,18 @@ window.onload = Task.async(function* () 
     is(tinyRenderedComponent.textContent,
       "EvalError",
       "Error Rep has expected text content for an EvalError in tiny mode");
   }
 
   function testInternalError() {
     // Test object = `new InternalError("InternalError message")`
     const stub = getGripStub("testInternalError");
-    const renderedRep = shallowRenderComponent(Rep, {object: stub});
-    is(renderedRep.type, ErrorRep.rep,
-      `Rep correctly selects ${ErrorRep.rep.displayName} for InternalError object`);
+    is(getRep(stub), ErrorRep.rep,
+      `Rep correctly selects Error Rep for InternalError object`);
 
     const renderedComponent = renderComponent(ErrorRep.rep, {object: stub});
     is(renderedComponent.textContent,
       "InternalError: InternalError message\n" +
       "Stack trace:\n" +
       "@debugger eval code:11:13\n",
       "Error Rep has expected text content for an InternalError");
 
@@ -155,19 +153,18 @@ window.onload = Task.async(function* () 
     is(tinyRenderedComponent.textContent,
       "InternalError",
       "Error Rep has expected text content for an InternalError in tiny mode");
   }
 
   function testRangeError() {
     // Test object = `new RangeError("RangeError message")`
     const stub = getGripStub("testRangeError");
-    const renderedRep = shallowRenderComponent(Rep, {object: stub});
-    is(renderedRep.type, ErrorRep.rep,
-      `Rep correctly selects ${ErrorRep.rep.displayName} for RangeError object`);
+    is(getRep(stub), ErrorRep.rep,
+      `Rep correctly selects Error Rep for RangeError object`);
 
     const renderedComponent = renderComponent(ErrorRep.rep, {object: stub});
     is(renderedComponent.textContent,
       "RangeError: RangeError message\n" +
       "Stack trace:\n" +
       "@debugger eval code:12:13\n",
       "Error Rep has expected text content for RangeError");
 
@@ -176,19 +173,18 @@ window.onload = Task.async(function* () 
     is(tinyRenderedComponent.textContent,
       "RangeError",
       "Error Rep has expected text content for RangeError in tiny mode");
   }
 
   function testReferenceError() {
     // Test object = `new ReferenceError("ReferenceError message"`
     const stub = getGripStub("testReferenceError");
-    const renderedRep = shallowRenderComponent(Rep, {object: stub});
-    is(renderedRep.type, ErrorRep.rep,
-      `Rep correctly selects ${ErrorRep.rep.displayName} for ReferenceError object`);
+    is(getRep(stub), ErrorRep.rep,
+      `Rep correctly selects Error Rep for ReferenceError object`);
 
     const renderedComponent = renderComponent(ErrorRep.rep, {object: stub});
     is(renderedComponent.textContent,
       "ReferenceError: ReferenceError message\n" +
       "Stack trace:\n" +
       "@debugger eval code:13:13\n",
       "Error Rep has expected text content for ReferenceError");
 
@@ -197,19 +193,18 @@ window.onload = Task.async(function* () 
     is(tinyRenderedComponent.textContent,
       "ReferenceError",
       "Error Rep has expected text content for ReferenceError in tiny mode");
   }
 
   function testSyntaxError() {
     // Test object = `new SyntaxError("SyntaxError message"`
     const stub = getGripStub("testSyntaxError");
-    const renderedRep = shallowRenderComponent(Rep, {object: stub});
-    is(renderedRep.type, ErrorRep.rep,
-      `Rep correctly selects ${ErrorRep.rep.displayName} for SyntaxError object`);
+    is(getRep(stub), ErrorRep.rep,
+      `Rep correctly selects Error Rep for SyntaxError object`);
 
     const renderedComponent = renderComponent(ErrorRep.rep, {object: stub});
     is(renderedComponent.textContent,
       "SyntaxError: SyntaxError message\n" +
       "Stack trace:\n" +
       "@debugger eval code:14:13\n",
       "Error Rep has expected text content for SyntaxError");
 
@@ -218,19 +213,18 @@ window.onload = Task.async(function* () 
     is(tinyRenderedComponent.textContent,
       "SyntaxError",
       "SyntaxError Rep has expected text content for SyntaxError in tiny mode");
   }
 
   function testTypeError() {
     // Test object = `new TypeError("TypeError message"`
     const stub = getGripStub("testTypeError");
-    const renderedRep = shallowRenderComponent(Rep, {object: stub});
-    is(renderedRep.type, ErrorRep.rep,
-      `Rep correctly selects ${ErrorRep.rep.displayName} for TypeError`);
+    is(getRep(stub), ErrorRep.rep,
+      `Rep correctly selects Error Rep for TypeError`);
 
     const renderedComponent = renderComponent(ErrorRep.rep, {object: stub});
     is(renderedComponent.textContent,
       "TypeError: TypeError message\n" +
       "Stack trace:\n" +
       "@debugger eval code:15:13\n",
       "Error Rep has expected text content for TypeError");
 
@@ -239,19 +233,18 @@ window.onload = Task.async(function* () 
     is(tinyRenderedComponent.textContent,
       "TypeError",
       "Error Rep has expected text content for a TypeError in tiny mode");
   }
 
   function testURIError() {
     // Test object = `new URIError("URIError message")`
     const stub = getGripStub("testURIError");
-    const renderedRep = shallowRenderComponent(Rep, {object: stub});
-    is(renderedRep.type, ErrorRep.rep,
-      `Rep correctly selects ${ErrorRep.rep.displayName} for URIError object`);
+    is(getRep(stub), ErrorRep.rep,
+      `Rep correctly selects Error Rep for URIError object`);
 
     const renderedComponent = renderComponent(ErrorRep.rep, {object: stub});
     is(renderedComponent.textContent,
       "URIError: URIError message\n" +
       "Stack trace:\n" +
       "@debugger eval code:16:13\n",
       "Error Rep has expected text content for URIError");
 
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_event.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_event.html
@@ -15,24 +15,24 @@ Test Event rep
 <body>
 <pre id="test">
 <script src="head.js" type="application/javascript"></script>
 <script type="application/javascript">
 window.onload = Task.async(function* () {
   const {
     REPS,
     MODE,
+    getRep,
     getSelectableInInspectorGrips,
   } = browserRequire("devtools/client/shared/components/reps/reps");
-  let { Rep, Event } = REPS;
+  let { Event } = REPS;
 
   try {
     // Test that correct rep is chosen
-    const renderedRep = shallowRenderComponent(Rep, { object: getGripStub("testEvent") });
-    is(renderedRep.type, Event.rep, `Rep correctly selects ${Event.rep.displayName}`);
+    is(getRep(getGripStub("testEvent")), Event.rep, "Rep correctly selects Event Rep");
 
     yield testEvent();
     yield testMouseEvent();
     yield testKeyboardEvent();
     yield testKeyboardEventWithModifiers();
     yield testMessageEvent();
 
     yield testOnDomNodeMouseOver();
@@ -124,91 +124,70 @@ window.onload = Task.async(function* () 
   }
 
   function testOnDomNodeMouseOver() {
     const stub = getGripStub("testMouseEvent");
     const grips = getSelectableInInspectorGrips(stub);
 
     is(grips.length, 1, "the stub has one node grip");
 
-    const attachedActorIds = getStubAttachedActorIds(grips);
-
     let mouseOverValue;
     let onDOMNodeMouseOver = (object) => {
       mouseOverValue = object;
     };
     const renderedComponent = renderComponent(Event.rep, {
       object: stub,
       onDOMNodeMouseOver,
-      attachedActorIds,
     });
 
     const node = renderedComponent.querySelector(".objectBox-node");
     TestUtils.Simulate.mouseOver(node);
 
     is(mouseOverValue, grips[0], "onDOMNodeMouseOver is called with " +
       "the expected argument when mouseover is fired on the Rep");
   }
 
   function testOnDomNodeMouseOut() {
     const stub = getGripStub("testMouseEvent");
     const grips = getSelectableInInspectorGrips(stub);
     is(grips.length, 1, "the stub has one node grip");
 
-    const attachedActorIds = getStubAttachedActorIds(grips);
-
     let called = false;
     let onDOMNodeMouseOut = (object) => {
       called = true;
     };
     const renderedComponent = renderComponent(Event.rep, {
       object: stub,
       onDOMNodeMouseOut,
-      attachedActorIds
     });
 
     const node = renderedComponent.querySelector(".objectBox-node");
     TestUtils.Simulate.mouseOut(node);
 
     is(called, true, "onDOMNodeMouseOut is called when mouseout is fired on the Rep");
   }
 
   function testOnDomNodeInspectIconClick() {
     const stub = getGripStub("testMouseEvent");
     const grips = getSelectableInInspectorGrips(stub);
     is(grips.length, 1, "the stub has one node grip");
 
-    const attachedActorIds = getStubAttachedActorIds(grips);
-
     let inspectIconClickedValue = null;
     let onInspectIconClick = (object) => {
       inspectIconClickedValue = object;
     };
 
-    let renderedComponentWithoutInspectIcon = renderComponent(Event.rep, {
-      object: stub,
-      onInspectIconClick,
-      attachedActorIds: ["someOtherId"]
-    });
-    is(renderedComponentWithoutInspectIcon.querySelector(".open-inspector"), null,
-      "There isn't an inspect icon when the actor is not in attachedActorIds");
-
-    is(renderedComponentWithoutInspectIcon.querySelector(".open-inspector"), null,
-      "There isn't an inspect icon when attachedActorIds does not have keys " +
-      "matching grip event's target item");
-
     const renderedComponent = renderComponent(Event.rep, {
       object: stub,
       onInspectIconClick,
-      attachedActorIds
     });
 
     const icon = renderedComponent.querySelector(".open-inspector");
-    ok(icon !== null, "There is an icon as expected when passing a matching " +
-      "attachedActorIds item");
+    ok(icon !== null,
+      "There is an inspect icon when the node is connected to the DOM tree");
 
     TestUtils.Simulate.click(icon);
 
     is(inspectIconClickedValue, grips[0],
       "onInspectIconClick is called with the expected argument " +
       "when the inspect icon is clicked");
   }
 
@@ -339,16 +318,17 @@ window.onload = Task.async(function* () 
               "extensible": true,
               "frozen": false,
               "sealed": false,
               "ownPropertyLength": 0,
               "preview": {
                 "kind": "DOMNode",
                 "nodeType": 1,
                 "nodeName": "div",
+                "isConnected": true,
                 "attributes": {
                   "id": "test"
                 },
                 "attributesLength": 1
               }
             }
           }
         };
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_failure.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_failure.html
@@ -13,18 +13,21 @@ Test fallback for rep rendering when a r
   <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"></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;
+    const {
+      REPS,
+      getRep,
+    } = browserRequire("devtools/client/shared/components/reps/reps");
+    let { ArrayRep, RegExp } = REPS;
 
     // Force the RegExp rep to crash by creating RegExp grip that throws when accessing
     // the displayString property
     let gripStub = {
       "type": "object",
       "class": "RegExp",
       "actor": "server1.conn22.obj39",
       "extensible": true,
@@ -32,18 +35,17 @@ window.onload = Task.async(function* () 
       "sealed": false,
       "ownPropertyLength": 1,
       get displayString() {
         throw new Error("failure");
       }
     };
 
     // Test that correct rep is chosen.
-    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
-    is(renderedRep.type, RegExp.rep, `Rep correctly selects ${RegExp.rep.displayName}`);
+    is(getRep(gripStub), RegExp.rep, "Rep correctly selects RegExp Rep");
 
     // Test fallback message is displayed when rendering bad rep directly.
     let renderedComponent = renderComponent(RegExp.rep, { object: gripStub });
     is(renderedComponent.textContent, "Invalid object", "Fallback rendering has expected text content");
 
     // Test fallback message is displayed when bad rep is nested in another rep.
     renderedComponent = renderComponent(ArrayRep.rep, { object: [1, gripStub, 2] });
     is(renderedComponent.textContent, "[ 1, Invalid object, 2 ]", "Fallback rendering has expected text content");
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_function.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_function.html
@@ -12,26 +12,29 @@ Test Func rep
   <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"></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 {
+    REPS,
+    MODE,
+    getRep,
+   } = browserRequire("devtools/client/shared/components/reps/reps");
+  let { Func } = REPS;
 
   const componentUnderTest = Func;
 
   try {
     // Test that correct rep is chosen
     const gripStub = getGripStub("testNamed");
-    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
-    is(renderedRep.type, Func.rep, `Rep correctly selects ${Func.rep.displayName}`);
+    is(getRep(gripStub), Func.rep, "Rep correctly selects Func Rep");
 
     yield testNamed();
     yield testVarNamed();
     yield testAnon();
     yield testLongName();
     yield testAsyncFunction();
     yield testAnonAsyncFunction();
     yield testGeneratorFunction();
--- 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
@@ -15,19 +15,20 @@ Test GripArray rep
 <body>
 <pre id="test">
 <script src="head.js" type="application/javascript"></script>
 <script type="application/javascript">
 window.onload = Task.async(function* () {
   const {
     REPS,
     MODE,
+    getRep,
     getSelectableInInspectorGrips,
   } = browserRequire("devtools/client/shared/components/reps/reps");
-  let { Rep, GripArray } = REPS;
+  let { GripArray } = REPS;
 
   let componentUnderTest = GripArray;
   const maxLength = {
     short: 3,
     long: 10
   };
 
   try {
@@ -55,18 +56,17 @@ window.onload = Task.async(function* () 
   }
 
   function testBasic() {
     // Test array: `[]`
     const testName = "testBasic";
 
     // Test that correct rep is chosen
     const gripStub = getGripStub("testBasic");
-    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
-    is(renderedRep.type, GripArray.rep, `Rep correctly selects ${GripArray.rep.displayName}`);
+    is(getRep(gripStub), GripArray.rep, "Rep correctly selects GripArray Rep");
 
     // Test rendering
     const defaultOutput = `Array []`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
@@ -323,26 +323,23 @@ window.onload = Task.async(function* () 
   }
 
   function testOnDomNodeMouseOver() {
     const stub = getGripStub("testNodeList");
     const grips = getSelectableInInspectorGrips(stub);
 
     is(grips.length, 3, "the stub has three node grips");
 
-    const attachedActorIds = getStubAttachedActorIds(grips);
-
     let mouseOverValue;
     let onDOMNodeMouseOver = (object) => {
       mouseOverValue = object;
     };
     const renderedComponent = renderComponent(GripArray.rep, {
       object: stub,
       onDOMNodeMouseOver,
-      attachedActorIds,
     });
 
     const nodes = renderedComponent.querySelectorAll(".objectBox-node");
     is(nodes.length, 3, "There are three node elements");
     nodes.forEach((node, index) => {
       TestUtils.Simulate.mouseOver(node);
 
       is(mouseOverValue, grips[index],
@@ -352,65 +349,57 @@ window.onload = Task.async(function* () 
   }
 
   function testOnDomNodeMouseOut() {
     const stub = getGripStub("testNodeList");
     const grips = getSelectableInInspectorGrips(stub);
 
     is(grips.length, 3, "the stub has three node grips");
 
-    const attachedActorIds = getStubAttachedActorIds(grips);
-
     let called = 0;
     let onDOMNodeMouseOut = (object) => {
       called++;
     };
     const renderedComponent = renderComponent(GripArray.rep, {
       object: stub,
       onDOMNodeMouseOut,
-      attachedActorIds,
     });
 
     const nodes = renderedComponent.querySelectorAll(".objectBox-node");
     info("Simulating mouseout on each node");
     Array.from(nodes).forEach(node => TestUtils.Simulate.mouseOut(node));
 
     is(called, 3, "onDOMNodeMouseOut is called when mouseout is fired on each NodeRep");
   }
 
   function testOnDomNodeInspectIconClick() {
-    const stub = getGripStub("testNodeList");
-    const grips = getSelectableInInspectorGrips(stub);
-
-    is(grips.length, 3, "the stub has three node grips");
-
-    const attachedActorIds = getStubAttachedActorIds(grips);
-
     let inspectIconClickedValue = null;
     let onInspectIconClick = (object) => {
       inspectIconClickedValue = object;
     };
 
     let renderedComponentWithoutInspectIcon = renderComponent(GripArray.rep, {
-      object: stub,
+      object: getGripStub("testDisconnectedNodeList"),
       onInspectIconClick,
-      attachedActorIds: ["someOtherId"],
     });
     is(renderedComponentWithoutInspectIcon.querySelector(".open-inspector"), null,
-      "There isn't an inspect icon when the actor is not in attachedActorIds");
+      "There isn't an inspect icon when the nodes are not connected to the DOM tree");
 
+    const stub = getGripStub("testNodeList");
+    const grips = getSelectableInInspectorGrips(stub);
+
+    is(grips.length, 3, "the stub has three node grips");
     const renderedComponent = renderComponent(GripArray.rep, {
       object: stub,
       onInspectIconClick,
-      attachedActorIds,
     });
 
     const icons = renderedComponent.querySelectorAll(".open-inspector");
     is(icons.length, grips.length,
-      "There is an icon for each grip array item with a matching attachedNodeFront");
+      "There is an icon for each node connected to the DOM tree");
 
     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");
     });
@@ -673,16 +662,17 @@ window.onload = Task.async(function* () 
                 "extensible": true,
                 "frozen": false,
                 "sealed": false,
                 "ownPropertyLength": 0,
                 "preview": {
                   "kind": "DOMNode",
                   "nodeType": 1,
                   "nodeName": "button",
+                  "isConnected": true,
                   "attributes": {
                     "id": "btn-1",
                     "class": "btn btn-log",
                     "type": "button"
                   },
                   "attributesLength": 3
                 }
               },
@@ -693,16 +683,17 @@ window.onload = Task.async(function* () 
                 "extensible": true,
                 "frozen": false,
                 "sealed": false,
                 "ownPropertyLength": 0,
                 "preview": {
                   "kind": "DOMNode",
                   "nodeType": 1,
                   "nodeName": "button",
+                  "isConnected": true,
                   "attributes": {
                     "id": "btn-2",
                     "class": "btn btn-err",
                     "type": "button"
                   },
                   "attributesLength": 3
                 }
               },
@@ -713,16 +704,97 @@ window.onload = Task.async(function* () 
                 "extensible": true,
                 "frozen": false,
                 "sealed": false,
                 "ownPropertyLength": 0,
                 "preview": {
                   "kind": "DOMNode",
                   "nodeType": 1,
                   "nodeName": "button",
+                  "isConnected": true,
+                  "attributes": {
+                    "id": "btn-3",
+                    "class": "btn btn-count",
+                    "type": "button"
+                  },
+                  "attributesLength": 3
+                }
+              }
+            ]
+          }
+        };
+
+      case "testDisconnectedNodeList":
+        return {
+          "type": "object",
+          "actor": "server1.conn1.child1/obj51",
+          "class": "NodeList",
+          "extensible": true,
+          "frozen": false,
+          "sealed": false,
+          "ownPropertyLength": 3,
+          "preview": {
+            "kind": "ArrayLike",
+            "length": 3,
+            "items": [
+              {
+                "type": "object",
+                "actor": "server1.conn1.child1/obj52",
+                "class": "HTMLButtonElement",
+                "extensible": true,
+                "frozen": false,
+                "sealed": false,
+                "ownPropertyLength": 0,
+                "preview": {
+                  "kind": "DOMNode",
+                  "nodeType": 1,
+                  "nodeName": "button",
+                  "isConnected": false,
+                  "attributes": {
+                    "id": "btn-1",
+                    "class": "btn btn-log",
+                    "type": "button"
+                  },
+                  "attributesLength": 3
+                }
+              },
+              {
+                "type": "object",
+                "actor": "server1.conn1.child1/obj53",
+                "class": "HTMLButtonElement",
+                "extensible": true,
+                "frozen": false,
+                "sealed": false,
+                "ownPropertyLength": 0,
+                "preview": {
+                  "kind": "DOMNode",
+                  "nodeType": 1,
+                  "nodeName": "button",
+                  "isConnected": false,
+                  "attributes": {
+                    "id": "btn-2",
+                    "class": "btn btn-err",
+                    "type": "button"
+                  },
+                  "attributesLength": 3
+                }
+              },
+              {
+                "type": "object",
+                "actor": "server1.conn1.child1/obj54",
+                "class": "HTMLButtonElement",
+                "extensible": true,
+                "frozen": false,
+                "sealed": false,
+                "ownPropertyLength": 0,
+                "preview": {
+                  "kind": "DOMNode",
+                  "nodeType": 1,
+                  "nodeName": "button",
+                  "isConnected": false,
                   "attributes": {
                     "id": "btn-3",
                     "class": "btn btn-count",
                     "type": "button"
                   },
                   "attributesLength": 3
                 }
               }
--- 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
@@ -17,19 +17,20 @@ Test GripMap rep
 <script src="head.js" type="application/javascript"></script>
 <script type="application/javascript">
 "use strict";
 
 window.onload = Task.async(function* () {
   const {
     REPS,
     MODE,
+    getRep,
     getSelectableInInspectorGrips,
   } = browserRequire("devtools/client/shared/components/reps/reps");
-  let { Rep, GripMap } = REPS;
+  let { GripMap } = REPS;
 
   const componentUnderTest = GripMap;
 
   try {
     yield testEmptyMap();
     yield testSymbolKeyedMap();
     yield testWeakMap();
 
@@ -50,18 +51,17 @@ window.onload = Task.async(function* () 
   }
 
   function testEmptyMap() {
     // Test object: `new Map()`
     const testName = "testEmptyMap";
 
     // Test that correct rep is chosen
     const gripStub = getGripStub("testEmptyMap");
-    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
-    is(renderedRep.type, GripMap.rep, `Rep correctly selects ${GripMap.rep.displayName}`);
+    is(getRep(gripStub), GripMap.rep, "Rep correctly selects GripMap Rep");
 
     // Test rendering
     const defaultOutput = `Map {  }`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
@@ -113,18 +113,17 @@ window.onload = Task.async(function* () 
   }
 
   function testWeakMap() {
     // Test object: `new WeakMap([[{a: "key-a"}, "value-a"]])`
     const testName = "testWeakMap";
 
     // Test that correct rep is chosen
     const gripStub = getGripStub("testWeakMap");
-    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
-    is(renderedRep.type, GripMap.rep, `Rep correctly selects ${GripMap.rep.displayName}`);
+    is(getRep(gripStub), GripMap.rep, "Rep correctly selects GripMap Rep");
 
     // Test rendering
     const defaultOutput = `WeakMap { Object: "value-a" }`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
@@ -249,47 +248,43 @@ window.onload = Task.async(function* () 
   }
 
   function testOnDomNodeMouseOver() {
     const nodeValuedStub = getGripStub("testNodeValuedMap");
     const nodeKeyedStub = getGripStub("testNodeKeyedMap");
 
     const valuesGrips = getSelectableInInspectorGrips(nodeValuedStub);
     is(valuesGrips.length, 3, "the stub has three node grips");
-    const valuesattachedActorIds = getStubAttachedActorIds(valuesGrips);
 
     const keysGrips = getSelectableInInspectorGrips(nodeKeyedStub);
     is(keysGrips.length, 3, "the stub has three node grips");
-    const keysAttachedActorIds = getStubAttachedActorIds(keysGrips);
 
     let mouseOverValue;
     let onDOMNodeMouseOver = (object) => {
       mouseOverValue = object;
     };
 
     info("Testing onDOMNodeMouseOver on node valued Map");
     const nodeValuedRenderedComponent = renderComponent(GripMap.rep, {
       object: nodeValuedStub,
       onDOMNodeMouseOver,
-      attachedActorIds: valuesattachedActorIds,
     });
 
     let nodes = nodeValuedRenderedComponent.querySelectorAll(".objectBox-node");
     nodes.forEach((node, index) => {
       TestUtils.Simulate.mouseOver(node);
       is(mouseOverValue, valuesGrips[index],
         "onDOMNodeMouseOver is called with the expected argument " +
         "when mouseover is fired on the Rep");
     });
 
     info("Testing onDOMNodeMouseOver on node keyed Map");
     const nodeKeyedRenderedComponent = renderComponent(GripMap.rep, {
       object: nodeKeyedStub,
       onDOMNodeMouseOver,
-      attachedActorIds: keysAttachedActorIds,
     });
 
     nodes = nodeKeyedRenderedComponent.querySelectorAll(".objectBox-node");
     nodes.forEach((node, index) => {
       TestUtils.Simulate.mouseOver(node);
       is(mouseOverValue, keysGrips[index],
         "onDOMNodeMouseOver is called with the expected argument " +
         "when mouseover is fired on the Rep");
@@ -297,45 +292,41 @@ window.onload = Task.async(function* () 
   }
 
   function testOnDomNodeMouseOut() {
     const nodeValuedStub = getGripStub("testNodeValuedMap");
     const nodeKeyedStub = getGripStub("testNodeKeyedMap");
 
     const valuesGrips = getSelectableInInspectorGrips(nodeValuedStub);
     is(valuesGrips.length, 3, "the stub has three node grips");
-    const valuesattachedActorIds = getStubAttachedActorIds(valuesGrips);
 
     const keysGrips = getSelectableInInspectorGrips(nodeKeyedStub);
     is(keysGrips.length, 3, "the stub has three node grips");
-    const keysAttachedActorIds = getStubAttachedActorIds(keysGrips);
 
     let called = 0;
     let onDOMNodeMouseOut = (object) => {
       called++;
     };
 
     info("Testing onDOMNodeMouseOut on node valued Map");
     const nodeValuedRenderedComponent = renderComponent(GripMap.rep, {
       object: nodeValuedStub,
       onDOMNodeMouseOut,
-      attachedActorIds: valuesattachedActorIds,
     });
 
     let nodes = nodeValuedRenderedComponent.querySelectorAll(".objectBox-node");
     info("Simulating mouseout on each value node");
     nodes.forEach((node, index) => TestUtils.Simulate.mouseOut(node));
     is(called, 3,
       "onDOMNodeMouseOut is called when mouseout is fired on each value NodeRep");
 
     info("Testing onDOMNodeMouseOut on node keyed Map");
     const nodeKeyedRenderedComponent = renderComponent(GripMap.rep, {
       object: nodeKeyedStub,
       onDOMNodeMouseOut,
-      attachedActorIds: keysAttachedActorIds,
     });
 
     nodes = nodeKeyedRenderedComponent.querySelectorAll(".objectBox-node");
     // Resets counter
     called = 0;
     info("Simulating mouseout on each key node");
     nodes.forEach((node, index) => TestUtils.Simulate.mouseOut(node));
     is(called, 3,
@@ -343,62 +334,57 @@ window.onload = Task.async(function* () 
   }
 
   function testOnDomNodeInspectIconClick() {
     const nodeValuedStub = getGripStub("testNodeValuedMap");
     const nodeKeyedStub = getGripStub("testNodeKeyedMap");
 
     const valuesGrips = getSelectableInInspectorGrips(nodeValuedStub);
     is(valuesGrips.length, 3, "the stub has three node grips");
-    const valuesattachedActorIds = getStubAttachedActorIds(valuesGrips);
 
     const keysGrips = getSelectableInInspectorGrips(nodeKeyedStub);
     is(keysGrips.length, 3, "the stub has three node grips");
-    const keysAttachedActorIds = getStubAttachedActorIds(keysGrips);
 
     let inspectIconClickedValue = null;
     let onInspectIconClick = (object) => {
       inspectIconClickedValue = object;
     };
 
     const renderedComponentWithoutInspectIcon = renderComponent(GripMap.rep, {
-      object: nodeValuedStub,
+      object: getGripStub("testDisconnectedNodeValuedMap"),
       onInspectIconClick,
-      attachedActorIds: [],
     });
     is(renderedComponentWithoutInspectIcon.querySelector(".open-inspector"), null,
-      "There isn't an inspect icon when the actor is not in attachedActorIds");
+      "There isn't an inspect icon when nodes are not connected to the DOM tree");
 
     info("Testing onInspectIconClick on node valued Map");
     const nodeValuedRenderedComponent = renderComponent(GripMap.rep, {
       object: nodeValuedStub,
       onInspectIconClick,
-      attachedActorIds: valuesattachedActorIds,
     });
 
     let icons = nodeValuedRenderedComponent.querySelectorAll(".open-inspector");
     is(icons.length, valuesGrips.length,
-      "There is an icon for each map value with a matching attachedNodeFront");
+      "There is an icon for each node connected to the DOM tree");
 
     icons.forEach((icon, index) => {
       TestUtils.Simulate.click(icon);
       is(inspectIconClickedValue, valuesGrips[index], "onInspectIconClick is called " +
         "with the expected argument when the inspect icon is clicked");
     });
 
     info("Testing onInspectIconClick on node keyed Map");
     const nodeKeyedRenderedComponent = renderComponent(GripMap.rep, {
       object: nodeKeyedStub,
       onInspectIconClick,
-      attachedActorIds: keysAttachedActorIds,
     });
 
     icons = nodeKeyedRenderedComponent.querySelectorAll(".open-inspector");
     is(icons.length, keysGrips.length,
-      "There is an icon for each map key with a matching attachedNodeFront");
+      "There is an icon for each node connected to the DOM tree");
 
     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");
     });
   }
 
@@ -602,16 +588,102 @@ window.onload = Task.async(function* () 
               [
                 "key-d",
                 4
               ]
             ]
           }
         };
 
+      case "testDisconnectedNodeValuedMap":
+        return {
+          "type": "object",
+          "actor": "server1.conn1.child1/obj213",
+          "class": "Map",
+          "ownPropertyLength": 0,
+          "preview": {
+            "kind": "MapLike",
+            "size": 3,
+            "entries": [
+              [
+                "item-0",
+                {
+                  "type": "object",
+                  "actor": "server1.conn1.child1/obj214",
+                  "class": "HTMLButtonElement",
+                  "extensible": true,
+                  "frozen": false,
+                  "sealed": false,
+                  "ownPropertyLength": 0,
+                  "preview": {
+                    "kind": "DOMNode",
+                    "nodeType": 1,
+                    "nodeName": "button",
+                    "isConnected": false,
+                    "attributes": {
+                      "id": "btn-1",
+                      "class": "btn btn-log",
+                      "type": "button"
+                    },
+                    "attributesLength": 3
+                  }
+                }
+              ],
+              [
+                "item-1",
+                {
+                  "type": "object",
+                  "actor": "server1.conn1.child1/obj215",
+                  "class": "HTMLButtonElement",
+                  "extensible": true,
+                  "frozen": false,
+                  "sealed": false,
+                  "ownPropertyLength": 0,
+                  "preview": {
+                    "kind": "DOMNode",
+                    "nodeType": 1,
+                    "nodeName": "button",
+                    "isConnected": false,
+                    "attributes": {
+                      "id": "btn-2",
+                      "class": "btn btn-err",
+                      "type": "button"
+                    },
+                    "attributesLength": 3
+                  }
+                }
+              ],
+              [
+                "item-2",
+                {
+                  "type": "object",
+                  "actor": "server1.conn1.child1/obj216",
+                  "class": "HTMLButtonElement",
+                  "extensible": true,
+                  "frozen": false,
+                  "sealed": false,
+                  "ownPropertyLength": 0,
+                  "preview": {
+                    "kind": "DOMNode",
+                    "nodeType": 1,
+                    "nodeName": "button",
+                    "isConnected": false,
+                    "attributes": {
+                      "id": "btn-3",
+                      "class": "btn btn-count",
+                      "type": "button"
+                    },
+                    "attributesLength": 3
+                  }
+                }
+              ]
+            ]
+          }
+        };
+
       case "testNodeValuedMap":
         return {
           "type": "object",
           "actor": "server1.conn1.child1/obj213",
           "class": "Map",
           "ownPropertyLength": 0,
           "preview": {
             "kind": "MapLike",
@@ -626,16 +698,17 @@ window.onload = Task.async(function* () 
                   "extensible": true,
                   "frozen": false,
                   "sealed": false,
                   "ownPropertyLength": 0,
                   "preview": {
                     "kind": "DOMNode",
                     "nodeType": 1,
                     "nodeName": "button",
+                    "isConnected": true,
                     "attributes": {
                       "id": "btn-1",
                       "class": "btn btn-log",
                       "type": "button"
                     },
                     "attributesLength": 3
                   }
                 }
@@ -649,16 +722,17 @@ window.onload = Task.async(function* () 
                   "extensible": true,
                   "frozen": false,
                   "sealed": false,
                   "ownPropertyLength": 0,
                   "preview": {
                     "kind": "DOMNode",
                     "nodeType": 1,
                     "nodeName": "button",
+                    "isConnected": true,
                     "attributes": {
                       "id": "btn-2",
                       "class": "btn btn-err",
                       "type": "button"
                     },
                     "attributesLength": 3
                   }
                 }
@@ -672,16 +746,17 @@ window.onload = Task.async(function* () 
                   "extensible": true,
                   "frozen": false,
                   "sealed": false,
                   "ownPropertyLength": 0,
                   "preview": {
                     "kind": "DOMNode",
                     "nodeType": 1,
                     "nodeName": "button",
+                    "isConnected": true,
                     "attributes": {
                       "id": "btn-3",
                       "class": "btn btn-count",
                       "type": "button"
                     },
                     "attributesLength": 3
                   }
                 }
@@ -708,16 +783,17 @@ window.onload = Task.async(function* () 
                   "extensible": true,
                   "frozen": false,
                   "sealed": false,
                   "ownPropertyLength": 0,
                   "preview": {
                     "kind": "DOMNode",
                     "nodeType": 1,
                     "nodeName": "button",
+                    "isConnected": true,
                     "attributes": {
                       "id": "btn-1",
                       "class": "btn btn-log",
                       "type": "button"
                     },
                     "attributesLength": 3
                   }
                 },
@@ -731,16 +807,17 @@ window.onload = Task.async(function* () 
                   "extensible": true,
                   "frozen": false,
                   "sealed": false,
                   "ownPropertyLength": 0,
                   "preview": {
                     "kind": "DOMNode",
                     "nodeType": 1,
                     "nodeName": "button",
+                    "isConnected": true,
                     "attributes": {
                       "id": "btn-3",
                       "class": "btn btn-count",
                       "type": "button"
                     },
                     "attributesLength": 3
                   }
                 },
@@ -754,16 +831,17 @@ window.onload = Task.async(function* () 
                   "extensible": true,
                   "frozen": false,
                   "sealed": false,
                   "ownPropertyLength": 0,
                   "preview": {
                     "kind": "DOMNode",
                     "nodeType": 1,
                     "nodeName": "button",
+                    "isConnected": true,
                     "attributes": {
                       "id": "btn-2",
                       "class": "btn btn-err",
                       "type": "button"
                     },
                     "attributesLength": 3
                   }
                 },
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_grip.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_grip.html
@@ -15,19 +15,20 @@ Test grip rep
 <body>
 <pre id="test">
 <script src="head.js" type="application/javascript"></script>
 <script type="application/javascript">
 window.onload = Task.async(function* () {
   const {
     REPS,
     MODE,
+    getRep,
     getSelectableInInspectorGrips,
   } = browserRequire("devtools/client/shared/components/reps/reps");
-  let { Rep, Grip } = REPS;
+  let { Grip } = REPS;
 
   const componentUnderTest = Grip;
 
   try {
     yield testBasic();
     yield testBooleanObject();
     yield testNumberObject();
     yield testStringObject();
@@ -61,18 +62,17 @@ window.onload = Task.async(function* () 
   }
 
   function testBasic() {
     // Test object: `{}`
     const testName = "testBasic";
 
     // Test that correct rep is chosen
     const gripStub = getGripStub("testBasic");
-    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
-    is(renderedRep.type, Grip.rep, `Rep correctly selects ${Grip.rep.displayName}`);
+    is(getRep(gripStub), Grip.rep, "Rep correctly selects Grip Rep");
 
     // Test rendering
     const defaultOutput = `Object {  }`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
@@ -95,18 +95,17 @@ window.onload = Task.async(function* () 
   }
 
   function testBooleanObject() {
     // Test object: `new Boolean(true)`
     const testName = "testBooleanObject";
 
     // Test that correct rep is chosen
     const gripStub = getGripStub(testName);
-    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
-    is(renderedRep.type, Grip.rep, `Rep correctly selects ${Grip.rep.displayName}`);
+    is(getRep(gripStub), Grip.rep, "Rep correctly selects Grip Rep");
 
     // Test rendering
     const defaultOutput = `Boolean { true }`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
@@ -129,18 +128,17 @@ window.onload = Task.async(function* () 
   }
 
   function testNumberObject() {
     // Test object: `new Number(42)`
     const testName = "testNumberObject";
 
     // Test that correct rep is chosen
     const gripStub = getGripStub(testName);
-    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
-    is(renderedRep.type, Grip.rep, `Rep correctly selects ${Grip.rep.displayName}`);
+    is(getRep(gripStub), Grip.rep, "Rep correctly selects Grip Rep");
 
     // Test rendering
     const defaultOutput = `Number { 42 }`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
@@ -163,18 +161,17 @@ window.onload = Task.async(function* () 
   }
 
   function testStringObject() {
     // Test object: `new String("foo")`
     const testName = "testStringObject";
 
     // Test that correct rep is chosen
     const gripStub = getGripStub(testName);
-    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
-    is(renderedRep.type, Grip.rep, `Rep correctly selects ${Grip.rep.displayName}`);
+    is(getRep(gripStub), Grip.rep, "Rep correctly selects Grip Rep");
 
     // Test rendering
     const defaultOutput = `String { "foo" }`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
@@ -197,18 +194,17 @@ window.onload = Task.async(function* () 
   }
 
   function testProxy() {
     // Test object: `new Proxy({a:1},[1,2,3])`
     const testName = "testProxy";
 
     // Test that correct rep is chosen
     const gripStub = getGripStub(testName);
-    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
-    is(renderedRep.type, Grip.rep, `Rep correctly selects ${Grip.rep.displayName}`);
+    is(getRep(gripStub), Grip.rep, "Rep correctly selects Grip Rep");
 
     // Test rendering
     const defaultOutput = `Proxy { <target>: Object, <handler>: [3] }`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
@@ -231,18 +227,17 @@ window.onload = Task.async(function* () 
   }
 
   function testArrayBuffer() {
     // Test object: `new ArrayBuffer(10)`
     const testName = "testArrayBuffer";
 
     // Test that correct rep is chosen
     const gripStub = getGripStub(testName);
-    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
-    is(renderedRep.type, Grip.rep, `Rep correctly selects ${Grip.rep.displayName}`);
+    is(getRep(gripStub), Grip.rep, "Rep correctly selects Grip Rep");
 
     // Test rendering
     const defaultOutput = `ArrayBuffer { byteLength: 10 }`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
@@ -265,18 +260,17 @@ window.onload = Task.async(function* () 
   }
 
   function testSharedArrayBuffer() {
     // Test object: `new SharedArrayBuffer(5)`
     const testName = "testSharedArrayBuffer";
 
     // Test that correct rep is chosen
     const gripStub = getGripStub(testName);
-    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
-    is(renderedRep.type, Grip.rep, `Rep correctly selects ${Grip.rep.displayName}`);
+    is(getRep(gripStub), Grip.rep, "Rep correctly selects Grip Rep");
 
     // Test rendering
     const defaultOutput = `SharedArrayBuffer { byteLength: 5 }`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
@@ -299,18 +293,17 @@ window.onload = Task.async(function* () 
   }
 
   function testApplicationCache() {
     // Test object: `window.applicationCache`
     const testName = "testApplicationCache";
 
     // Test that correct rep is chosen
     const gripStub = getGripStub(testName);
-    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
-    is(renderedRep.type, Grip.rep, `Rep correctly selects ${Grip.rep.displayName}`);
+    is(getRep(gripStub), Grip.rep, "Rep correctly selects Grip Rep");
 
     // Test rendering
     const defaultOutput =
       "OfflineResourceList { status: 0, onchecking: null, onerror: null, 7 more… }";
 
     const modeTests = [
       {
         mode: undefined,
@@ -407,18 +400,17 @@ window.onload = Task.async(function* () 
   }
 
   function testNonEnumerableProps() {
     // Test object: `Object.defineProperty({}, "foo", {enumerable : false});`
     const testName = "testNonEnumerableProps";
 
     // Test that correct rep is chosen
     const gripStub = getGripStub("testNonEnumerableProps");
-    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
-    is(renderedRep.type, Grip.rep, `Rep correctly selects ${Grip.rep.displayName}`);
+    is(getRep(gripStub), Grip.rep, "Rep correctly selects Grip Rep");
 
     // Test rendering
     const defaultOutput = `Object {  }`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
@@ -532,29 +524,27 @@ window.onload = Task.async(function* () 
     testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
   }
 
   function testOnDomNodeMouseOver() {
     const stub = getGripStub("testObjectWithNodes");
 
     const grips = getSelectableInInspectorGrips(stub);
     is(grips.length, 2, "the stub has two node grips");
-    const attachedActorIds = getStubAttachedActorIds(grips);
 
     let mouseOverValue;
     let called = 0;
     let onDOMNodeMouseOver = (object) => {
       mouseOverValue = object;
       called++;
     };
 
     const renderedComponent = renderComponent(Grip.rep, {
       object: stub,
       onDOMNodeMouseOver,
-      attachedActorIds,
     });
 
     const nodes = renderedComponent.querySelectorAll(".objectBox-node");
     nodes.forEach((node, index) => {
       TestUtils.Simulate.mouseOver(node);
       is(mouseOverValue, grips[index],
         "onDOMNodeMouseOver is called with the expected argument " +
         "when mouseover is fired on the Rep");
@@ -563,69 +553,60 @@ window.onload = Task.async(function* () 
       "onDOMNodeMouseOver is called when mouseOverValue is fired on each NodeRep");
   }
 
   function testOnDomNodeMouseOut() {
     const stub = getGripStub("testObjectWithNodes");
 
     const grips = getSelectableInInspectorGrips(stub);
     is(grips.length, 2, "the stub has two node grips");
-    const attachedActorIds = getStubAttachedActorIds(grips);
 
     let called = 0;
     let onDOMNodeMouseOut = (object) => {
       called++;
     };
 
     const renderedComponent = renderComponent(Grip.rep, {
       object: stub,
       onDOMNodeMouseOut,
-      attachedActorIds,
     });
 
     const nodes = renderedComponent.querySelectorAll(".objectBox-node");
     info("Simulating mouseout on each node");
     Array.from(nodes).forEach(node => TestUtils.Simulate.mouseOut(node));
 
     is(called, 2, "onDOMNodeMouseOut is called when mouseout is fired on each NodeRep");
   }
 
   function testOnDomNodeInspectIconClick() {
     const stub = getGripStub("testObjectWithNodes");
 
     const grips = getSelectableInInspectorGrips(stub);
     is(grips.length, 2, "the stub has two node grips");
-    const attachedActorIds = getStubAttachedActorIds(grips);
 
     let inspectIconClickedValue = null;
     let onInspectIconClick = (object) => {
       inspectIconClickedValue = object;
     };
 
     let renderedComponentWithoutInspectIcon = renderComponent(Grip.rep, {
-      object: stub,
+      object: getGripStub("testObjectWithDisconnectedNodes"),
       onInspectIconClick,
-      attachedActorIds: ["someOtherId"],
     });
     is(renderedComponentWithoutInspectIcon.querySelector(".open-inspector"), null,
-      "There isn't an inspect icon when the actor is not in attachedActorIds");
-
-    is(renderedComponentWithoutInspectIcon.querySelector(".open-inspector"), null,
-      "There isn't an inspect icon when attachedActorIds does not have keys " +
-      "matching grip properties");
+      "There isn't an inspect icon when the node is not connected to the DOM tree");
 
     const renderedComponent = renderComponent(Grip.rep, {
       object: stub,
       onInspectIconClick,
-      attachedActorIds,
     });
 
     const icons = renderedComponent.querySelectorAll(".open-inspector");
     is(icons.length, 2,
-      "There is an icon for each grip property matching an attachedNodeFront");
+      "There is an icon for each node connected to the DOM tree");
 
     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");
     });
   }
@@ -1188,16 +1169,82 @@ window.onload = Task.async(function* () 
                   "extensible": true,
                   "frozen": false,
                   "sealed": false,
                   "ownPropertyLength": 0,
                   "preview": {
                     "kind": "DOMNode",
                     "nodeType": 1,
                     "nodeName": "button",
+                    "isConnected": true,
+                    "attributes": {
+                      "id": "btn-1",
+                      "class": "btn btn-log",
+                      "type": "button"
+                    },
+                    "attributesLength": 3
+                  }
+                }
+              },
+              "bar": {
+                "configurable": true,
+                "enumerable": true,
+                "writable": true,
+                "value": {
+                  "type": "object",
+                  "actor": "server1.conn1.child1/obj216",
+                  "class": "HTMLButtonElement",
+                  "extensible": true,
+                  "frozen": false,
+                  "sealed": false,
+                  "ownPropertyLength": 0,
+                  "preview": {
+                    "kind": "DOMNode",
+                    "nodeType": 1,
+                    "nodeName": "button",
+                    "isConnected": true,
+                    "attributes": {
+                      "id": "btn-2",
+                      "class": "btn btn-err",
+                      "type": "button"
+                    },
+                    "attributesLength": 3
+                  }
+                }
+              }
+            },
+            "ownPropertiesLength": 2,
+            "safeGetterValues": {}
+          }
+        };
+      case "testObjectWithDisconnectedNodes":
+        return {
+          "type": "object",
+          "actor": "server1.conn1.child1/obj214",
+          "class": "Object",
+          "ownPropertyLength": 2,
+          "preview": {
+            "kind": "Object",
+            "ownProperties": {
+              "foo": {
+                "configurable": true,
+                "enumerable": true,
+                "writable": true,
+                "value": {
+                  "type": "object",
+                  "actor": "server1.conn1.child1/obj215",
+                  "class": "HTMLButtonElement",
+                  "extensible": true,
+                  "frozen": false,
+                  "sealed": false,
+                  "ownPropertyLength": 0,
+                  "preview": {
+                    "kind": "DOMNode",
+                    "nodeType": 1,
+                    "nodeName": "button",
                     "attributes": {
                       "id": "btn-1",
                       "class": "btn btn-log",
                       "type": "button"
                     },
                     "attributesLength": 3
                   }
                 }
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_infinity.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_infinity.html
@@ -14,44 +14,43 @@ Test Infinity rep
 </head>
 <body>
 <pre id="test">
 <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;
+  const {
+    REPS,
+    getRep,
+  } = browserRequire("devtools/client/shared/components/reps/reps");
+  let { InfinityRep } = REPS;
 
   try {
     yield testInfinity();
     yield testNegativeInfinity();
   } catch (e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
   function testInfinity() {
     const stub = getGripStub("testInfinity");
-    const renderedRep = shallowRenderComponent(Rep, { object: stub });
-    is(renderedRep.type, InfinityRep.rep,
-      `Rep correctly selects ${InfinityRep.rep.displayName} for Infinity value`);
+    is(getRep(stub), InfinityRep.rep, "Rep correctly selects Infinity Rep");
 
     const renderedComponent = renderComponent(InfinityRep.rep, { object: stub });
     is(renderedComponent.textContent, "Infinity",
       "Infinity rep has expected text content for Infinity");
   }
 
   function testNegativeInfinity() {
     const stub = getGripStub("testNegativeInfinity");
-    const renderedRep = shallowRenderComponent(Rep, { object: stub });
-    is(renderedRep.type, InfinityRep.rep,
-      `Rep correctly selects ${InfinityRep.rep.displayName} for negative Infinity value`);
+    is(getRep(stub), InfinityRep.rep, "Rep correctly selects Infinity Rep");
 
     const renderedComponent = renderComponent(InfinityRep.rep, { object: stub });
     is(renderedComponent.textContent, "-Infinity",
       "Infinity rep has expected text content for negative Infinity");
   }
 
   function getGripStub(name) {
     switch (name) {
--- 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
@@ -12,24 +12,26 @@ Test LongString rep
   <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"></script>
 <script type="application/javascript">
 window.onload = Task.async(function* () {
-  const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
-  let { Rep, LongStringRep } = REPS;
+  const {
+    REPS,
+    getRep,
+  } = browserRequire("devtools/client/shared/components/reps/reps");
+  let { LongStringRep } = REPS;
 
   try {
     // Test that correct rep is chosen
-    const renderedRep = shallowRenderComponent(Rep, { object: getGripStub("testMultiline") });
-    is(renderedRep.type, LongStringRep.rep,
-      `Rep correctly selects ${LongStringRep.rep.displayName}`);
+    is(getRep(getGripStub("testMultiline")), LongStringRep.rep,
+      "Rep correctly selects LongString Rep");
 
     // Test rendering
     yield testMultiline();
     yield testMultilineOpen();
     yield testFullText();
     yield testMultilineLimit();
     yield testUseQuotes();
   } catch (e) {
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_nan.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_nan.html
@@ -14,34 +14,35 @@ Test NaN rep
 </head>
 <body>
 <pre id="test">
 <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;
+  const {
+    REPS,
+    getRep,
+  } = browserRequire("devtools/client/shared/components/reps/reps");
+  let { NaNRep } = REPS;
 
   try {
     yield testNaN();
   } catch (e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
   function testNaN() {
     const stub = {
       type: "NaN"
     };
-    const renderedRep = shallowRenderComponent(Rep, {object: stub});
-    is(renderedRep.type, NaNRep.rep,
-      `Rep correctly selects ${NaNRep.rep.displayName} for NaN value`);
+    is(getRep(stub), NaNRep.rep, "Rep correctly selects NaN Rep");
 
     const renderedComponent = renderComponent(NaNRep.rep, {object: stub});
     is(renderedComponent.textContent, "NaN", "NaN rep has expected text content");
   }
 });
 </script>
 </pre>
 </body>
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_null.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_null.html
@@ -13,26 +13,28 @@ Test Null rep
   <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"></script>
 <script type="application/javascript">
 window.onload = Task.async(function* () {
   try {
-    const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
-    let { Rep, Null } = REPS;
+    const {
+      REPS,
+      getRep,
+    } = browserRequire("devtools/client/shared/components/reps/reps");
+    let { Null } = REPS;
 
     let gripStub = {
       "type": "null"
     };
 
     // Test that correct rep is chosen
-    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
-    is(renderedRep.type, Null.rep, `Rep correctly selects ${Null.rep.displayName}`);
+    is(getRep(gripStub), Null.rep, "Rep correctly selects Null Rep");
 
     // Test rendering
     const renderedComponent = renderComponent(Null.rep, { object: gripStub });
     is(renderedComponent.textContent, "null", "Null rep has expected text content");
   } catch(e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_number.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_number.html
@@ -12,53 +12,53 @@ Test Number rep
   <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"></script>
 <script type="application/javascript">
 window.onload = Task.async(function* () {
-  const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
-  let { Rep, Number } = REPS;
+  const {
+    REPS,
+    getRep,
+  } = browserRequire("devtools/client/shared/components/reps/reps");
+  let { Number } = REPS;
 
   try {
     yield testInt();
     yield testBoolean();
     yield testNegativeZero();
     yield testUnsafeInt();
   } catch(e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
 
   function testInt() {
-    const renderedRep = shallowRenderComponent(Rep, { object: getGripStub("testInt") });
-    is(renderedRep.type, Number.rep, `Rep correctly selects ${Number.rep.displayName} for integer value`);
+    is(getRep(getGripStub("testInt")), Number.rep, "Rep correctly selects Number Rep");
 
     const renderedComponent = renderComponent(Number.rep, { object: getGripStub("testInt") });
     is(renderedComponent.textContent, "5", "Number rep has expected text content for integer");
   }
 
   function testBoolean() {
-    const renderedRep = shallowRenderComponent(Rep, { object: getGripStub("testTrue") });
-    is(renderedRep.type, Number.rep, `Rep correctly selects ${Number.rep.displayName} for boolean value`);
+    is(getRep(getGripStub("testTrue")), Number.rep, "Rep correctly selects Number Rep for boolean value");
 
     let renderedComponent = renderComponent(Number.rep, { object: getGripStub("testTrue") });
     is(renderedComponent.textContent, "true", "Number rep has expected text content for boolean true");
 
     renderedComponent = renderComponent(Number.rep, { object: getGripStub("testFalse") });
     is(renderedComponent.textContent, "false", "Number rep has expected text content for boolean false");
   }
 
   function testNegativeZero() {
-    const renderedRep = shallowRenderComponent(Rep, { object: getGripStub("testNegZeroGrip") });
-    is(renderedRep.type, Number.rep, `Rep correctly selects ${Number.rep.displayName} for negative zero value`);
+    is(getRep(getGripStub("testNegZeroGrip")), Number.rep, "Rep correctly selects Number Rep for negative zero value");
 
     let renderedComponent = renderComponent(Number.rep, { object: getGripStub("testNegZeroGrip") });
     is(renderedComponent.textContent, "-0", "Number rep has expected text content for negative zero grip");
 
     renderedComponent = renderComponent(Number.rep, { object: getGripStub("testNegZeroValue") });
     is(renderedComponent.textContent, "-0", "Number rep has expected text content for negative zero value");
   }
 
--- 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
@@ -13,36 +13,38 @@ Test ObjectWithText rep
   <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"></script>
 <script type="application/javascript">
 window.onload = Task.async(function* () {
   try {
-    const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
-    let { Rep, ObjectWithText } = REPS;
+    const {
+      REPS,
+      getRep,
+    } = browserRequire("devtools/client/shared/components/reps/reps");
+    let { ObjectWithText } = REPS;
 
     let gripStub = {
       "type": "object",
       "class": "CSSStyleRule",
       "actor": "server1.conn3.obj273",
       "extensible": true,
       "frozen": false,
       "sealed": false,
       "ownPropertyLength": 0,
       "preview": {
         "kind": "ObjectWithText",
         "text": ".Shadow"
       }
     };
 
     // Test that correct rep is chosen
-    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
-    is(renderedRep.type, ObjectWithText.rep, `Rep correctly selects ${ObjectWithText.rep.displayName}`);
+    is(getRep(gripStub), ObjectWithText.rep, "Rep correctly selects ObjectWithText Rep");
 
     // 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,
--- 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
@@ -16,36 +16,38 @@ Test ObjectWithURL rep
 <pre id="test">
 <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;
+    const {
+      REPS,
+      getRep,
+    } = browserRequire("devtools/client/shared/components/reps/reps");
+    let { ObjectWithURL } = REPS;
 
     let gripStub = {
       "type": "object",
       "class": "Location",
       "actor": "server1.conn2.obj272",
       "extensible": true,
       "frozen": false,
       "sealed": false,
       "ownPropertyLength": 15,
       "preview": {
         "kind": "ObjectWithURL",
         "url": "https://www.mozilla.org/en-US/"
       }
     };
 
     // Test that correct rep is chosen
-    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
-    is(renderedRep.type, ObjectWithURL.rep, `Rep correctly selects ${ObjectWithURL.rep.displayName}`);
+    is(getRep(gripStub), ObjectWithURL.rep, "Rep correctly selects ObjectWithURL Rep");
 
     // 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
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_object.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_object.html
@@ -12,18 +12,22 @@ Test Obj rep
   <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"></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 {
+    REPS,
+    MODE,
+    getRep,
+  } = browserRequire("devtools/client/shared/components/reps/reps");
+  let { Obj } = REPS;
 
   const componentUnderTest = Obj;
 
   try {
     yield testBasic();
 
     // Test property iterator
     yield testMaxProps();
@@ -49,18 +53,17 @@ window.onload = Task.async(function* () 
   } finally {
     SimpleTest.finish();
   }
 
   function testBasic() {
     const stub = {};
 
     // Test that correct rep is chosen
-    const renderedRep = shallowRenderComponent(Rep, { object: stub });
-    is(renderedRep.type, Obj.rep, `Rep correctly selects ${Obj.rep.displayName}`);
+    is(getRep(stub), Obj.rep, "Rep correctly selects Obj Rep");
 
     // Test rendering
     const defaultOutput = `Object`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_promise.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_promise.html
@@ -17,19 +17,20 @@ Test Promise rep
 <script src="head.js" type="application/javascript"></script>
 <script type="application/javascript">
 "use strict";
 
 window.onload = Task.async(function* () {
   const {
     REPS,
     MODE,
+    getRep,
     getSelectableInInspectorGrips,
   } = browserRequire("devtools/client/shared/components/reps/reps");
-  let { Rep, PromiseRep } = REPS;
+  let { PromiseRep } = REPS;
 
   const componentUnderTest = PromiseRep;
 
   try {
     yield testPending();
     yield testFulfilledWithNumber();
     yield testFulfilledWithString();
     yield testFulfilledWithObject();
@@ -46,19 +47,18 @@ window.onload = Task.async(function* () 
     SimpleTest.finish();
   }
 
   function testPending() {
     // Test object = `new Promise((resolve, reject) => true)`
     const stub = getGripStub("testPending");
 
     // Test that correct rep is chosen.
-    const renderedRep = shallowRenderComponent(Rep, { object: stub });
-    is(renderedRep.type, PromiseRep.rep,
-      `Rep correctly selects ${PromiseRep.rep.displayName} for pending Promise`);
+    is(getRep(stub), PromiseRep.rep,
+      "Rep correctly selects PromiseRep Rep for pending Promise");
 
     // Test rendering
     const defaultOutput = `Promise { <state>: "pending" }`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
@@ -79,20 +79,18 @@ window.onload = Task.async(function* () 
 
     testRepRenderModes(modeTests, "testPending", componentUnderTest, stub);
   }
   function testFulfilledWithNumber() {
     // Test object = `Promise.resolve(42)`
     const stub = getGripStub("testFulfilledWithNumber");
 
     // Test that correct rep is chosen.
-    const renderedRep = shallowRenderComponent(Rep, { object: stub });
-    const {displayName} = PromiseRep.rep;
-    is(renderedRep.type, PromiseRep.rep,
-      `Rep correctly selects ${displayName} for Promise fulfilled with a number`);
+    is(getRep(stub), PromiseRep.rep,
+      "Rep correctly selects PromiseRep Rep for Promise fulfilled with a number");
 
     // Test rendering
     const defaultOutput = `Promise { <state>: "fulfilled", <value>: 42 }`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
@@ -113,20 +111,18 @@ window.onload = Task.async(function* () 
 
     testRepRenderModes(modeTests, "testFulfilledWithNumber", componentUnderTest, stub);
   }
   function testFulfilledWithString() {
     // Test object = `Promise.resolve("foo")`
     const stub = getGripStub("testFulfilledWithString");
 
     // Test that correct rep is chosen.
-    const renderedRep = shallowRenderComponent(Rep, { object: stub });
-    const {displayName} = PromiseRep.rep;
-    is(renderedRep.type, PromiseRep.rep,
-      `Rep correctly selects ${displayName} for Promise fulfilled with a string`);
+    is(getRep(stub), PromiseRep.rep,
+      "Rep correctly selects PromiseRep Rep for Promise fulfilled with a string");
 
     // Test rendering
     const defaultOutput = `Promise { <state>: "fulfilled", <value>: "foo" }`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
@@ -148,20 +144,18 @@ window.onload = Task.async(function* () 
     testRepRenderModes(modeTests, "testFulfilledWithString", componentUnderTest, stub);
   }
 
   function testFulfilledWithObject() {
     // Test object = `Promise.resolve({foo: "bar", baz: "boo"})`
     const stub = getGripStub("testFulfilledWithObject");
 
     // Test that correct rep is chosen.
-    const renderedRep = shallowRenderComponent(Rep, { object: stub });
-    const {displayName} = PromiseRep.rep;
-    is(renderedRep.type, PromiseRep.rep,
-      `Rep correctly selects ${displayName} for Promise fulfilled with an object`);
+    is(getRep(stub), PromiseRep.rep,
+      "Rep correctly selects PromiseRep Rep for Promise fulfilled with an object");
 
     // Test rendering
     const defaultOutput = `Promise { <state>: "fulfilled", <value>: Object }`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
@@ -183,20 +177,18 @@ window.onload = Task.async(function* () 
     testRepRenderModes(modeTests, "testFulfilledWithObject", componentUnderTest, stub);
   }
 
   function testFulfilledWithArray() {
     // Test object = `Promise.resolve([1,2,3])`
     const stub = getGripStub("testFulfilledWithArray");
 
     // Test that correct rep is chosen.
-    const renderedRep = shallowRenderComponent(Rep, { object: stub });
-    const {displayName} = PromiseRep.rep;
-    is(renderedRep.type, PromiseRep.rep,
-      `Rep correctly selects ${displayName} for Promise fulfilled with an array`);
+    is(getRep(stub), PromiseRep.rep,
+      "Rep correctly selects PromiseRep Rep for Promise fulfilled with an array");
 
     // Test rendering
     const defaultOutput = `Promise { <state>: "fulfilled", <value>: [3] }`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
@@ -218,84 +210,76 @@ window.onload = Task.async(function* () 
     testRepRenderModes(modeTests, "testFulfilledWithArray", componentUnderTest, stub);
   }
 
   function testOnDomNodeMouseOver() {
     const stub = getGripStub("testFulfilledWithNode");
 
     const grips = getSelectableInInspectorGrips(stub);
     is(grips.length, 1, "the stub has one node grip");
-    const attachedActorIds = getStubAttachedActorIds(grips);
 
     let mouseOverValue;
     let onDOMNodeMouseOver = (object) => {
       mouseOverValue = object;
     };
 
     const renderedComponent = renderComponent(PromiseRep.rep, {
       object: stub,
       onDOMNodeMouseOver,
-      attachedActorIds,
     });
 
     const node = renderedComponent.querySelector(".objectBox-node");
     TestUtils.Simulate.mouseOver(node);
 
     is(mouseOverValue, grips[0], "onDOMNodeMouseOver is called with " +
       "the expected argument when mouseover is fired on the node element");
   }
 
   function testOnDomNodeMouseOut() {
     const stub = getGripStub("testFulfilledWithNode");
 
     const grips = getSelectableInInspectorGrips(stub);
     is(grips.length, 1, "the stub has one node grip");
-    const attachedActorIds = getStubAttachedActorIds(grips);
 
     let called = false;
     let onDOMNodeMouseOut = (object) => {
       called = true;
     };
     const renderedComponent = renderComponent(PromiseRep.rep, {
       object: stub,
       onDOMNodeMouseOut,
-      attachedActorIds,
     });
 
     const node = renderedComponent.querySelector(".objectBox-node");
     TestUtils.Simulate.mouseOut(node);
 
     is(called, true,
       "onDOMNodeMouseOut is called when mouseout is fired on the node element");
   }
 
   function testOnDomNodeInspectIconClick() {
     const stub = getGripStub("testFulfilledWithNode");
-
     const grips = getSelectableInInspectorGrips(stub);
     is(grips.length, 1, "the stub has one node grip");
-    const attachedActorIds = getStubAttachedActorIds(grips);
 
     let inspectIconClickedValues = null;
     let onInspectIconClick = (object) => {
       inspectIconClickedValues = object;
     };
 
     let renderedComponentWithoutInspectIcon = renderComponent(PromiseRep.rep, {
-      object: stub,
+      object: getGripStub("testFulfilledWithDisconnectedNode"),
       onInspectIconClick,
-      attachedActorIds: ["someOtherId"],
     });
     is(renderedComponentWithoutInspectIcon.querySelector(".open-inspector"), null,
-      "There isn't an inspect icon when the actor is not in attachedActorIds");
+      "There isn't an inspect icon when the node is not connected to the DOM tree");
 
     const renderedComponent = renderComponent(PromiseRep.rep, {
       object: stub,
       onInspectIconClick,
-      attachedActorIds,
     });
 
     const icon = renderedComponent.querySelector(".open-inspector");
     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 " +
@@ -469,16 +453,56 @@ window.onload = Task.async(function* () 
               "extensible": true,
               "frozen": false,
               "sealed": false,
               "ownPropertyLength": 0,
               "preview": {
                 "kind": "DOMNode",
                 "nodeType": 1,
                 "nodeName": "button",
+                "isConnected": true,
+                "attributes": {
+                  "id": "btn-1",
+                  "class": "btn btn-log",
+                  "type": "button"
+                },
+                "attributesLength": 3
+              }
+            },
+            "creationTimestamp": 1480423091620.3716,
+            "timeToSettle": 0.02842400000372436
+          },
+          "ownPropertyLength": 0,
+          "preview": {
+            "kind": "Object",
+            "ownProperties": {},
+            "ownPropertiesLength": 0,
+            "safeGetterValues": {}
+          }
+        };
+      case "testFulfilledWithDisconnectedNode":
+        return {
+          "type": "object",
+          "actor": "server1.conn1.child1/obj217",
+          "class": "Promise",
+          "promiseState": {
+            "state": "fulfilled",
+            "value": {
+              "type": "object",
+              "actor": "server1.conn1.child1/obj218",
+              "class": "HTMLButtonElement",
+              "extensible": true,
+              "frozen": false,
+              "sealed": false,
+              "ownPropertyLength": 0,
+              "preview": {
+                "kind": "DOMNode",
+                "nodeType": 1,
+                "nodeName": "button",
+                "isConnected": false,
                 "attributes": {
                   "id": "btn-1",
                   "class": "btn btn-log",
                   "type": "button"
                 },
                 "attributesLength": 3
               }
             },
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_regexp.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_regexp.html
@@ -13,33 +13,35 @@ Test RegExp rep
   <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"></script>
 <script type="application/javascript">
 window.onload = Task.async(function* () {
   try {
-    const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
-    let { Rep, RegExp } = REPS;
+    const {
+      REPS,
+      getRep,
+    } = browserRequire("devtools/client/shared/components/reps/reps");
+    let { RegExp } = REPS;
 
     let gripStub = {
       "type": "object",
       "class": "RegExp",
       "actor": "server1.conn22.obj39",
       "extensible": true,
       "frozen": false,
       "sealed": false,
       "ownPropertyLength": 1,
       "displayString": "/ab+c/i"
     };
 
     // Test that correct rep is chosen
-    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
-    is(renderedRep.type, RegExp.rep, `Rep correctly selects ${RegExp.rep.displayName}`);
+    is(getRep(gripStub), RegExp.rep, "Rep correctly selects RegExp Rep");
 
     // 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,
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_string.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_string.html
@@ -12,18 +12,21 @@ Test String rep
   <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"></script>
 <script type="application/javascript">
 window.onload = Task.async(function* () {
-  const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
-  let { Rep, StringRep } = REPS;
+  const {
+    REPS,
+    getRep,
+   } = browserRequire("devtools/client/shared/components/reps/reps");
+  let { StringRep } = REPS;
 
   const test_cases = [{
     name: "testMultiline",
     props: {
       object: "aaaaaaaaaaaaaaaaaaaaa\nbbbbbbbbbbbbbbbbbbb\ncccccccccccccccc\n",
     },
     result: "\"aaaaaaaaaaaaaaaaaaaaa\\nbbbbbbbbbbbbbbbbbbb\\ncccccccccccccccc\\n\""
   }, {
@@ -70,22 +73,30 @@ window.onload = Task.async(function* () 
     result: "\"\\udc23\""
   }, {
     name: "testValidSurrogate",
     props: {
       object: "\ud83d\udeec",
       useQuotes: true
     },
     result: "\"\ud83d\udeec\""
+  }, {
+    name: "testNoEscapeWhitespace",
+    props: {
+      object: "line 1\r\nline 2\n\tline 3",
+      useQuotes: true,
+      escapeWhitespace: false,
+    },
+    result: "\"line 1\r\nline 2\n\tline 3\""
   }];
 
   try {
     // Test that correct rep is chosen
-    const renderedRep = shallowRenderComponent(Rep, test_cases[0].props);
-    is(renderedRep.type, StringRep.rep, `Rep correctly selects ${StringRep.rep.displayName}`);
+    is(getRep(test_cases[0].props.object), StringRep.rep,
+      "Rep correctly selects String Rep");
 
     // Test rendering
     for (let test of test_cases) {
       const renderedComponent = renderComponent(StringRep.rep, test.props);
       is(renderedComponent.textContent, test.result, "String rep " + test.name);
     }
   } catch(e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_stylesheet.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_stylesheet.html
@@ -13,36 +13,38 @@ Test Stylesheet rep
   <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"></script>
 <script type="application/javascript">
 window.onload = Task.async(function* () {
   try {
-    const { REPS } = browserRequire("devtools/client/shared/components/reps/reps");
-    let { Rep, StyleSheet } = REPS;
+    const {
+      REPS,
+      getRep,
+    } = browserRequire("devtools/client/shared/components/reps/reps");
+    let { StyleSheet } = REPS;
 
     let gripStub = {
       "type": "object",
       "class": "CSSStyleSheet",
       "actor": "server1.conn2.obj1067",
       "extensible": true,
       "frozen": false,
       "sealed": false,
       "ownPropertyLength": 0,
       "preview": {
         "kind": "ObjectWithURL",
         "url": "https://example.com/styles.css"
       }
     };
 
     // Test that correct rep is chosen
-    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
-    is(renderedRep.type, StyleSheet.rep, `Rep correctly selects ${StyleSheet.rep.displayName}`);
+    is(getRep(gripStub), StyleSheet.rep, "Rep correctly selects StyleSheet Rep");
 
     // 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,
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_symbol.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_symbol.html
@@ -15,37 +15,35 @@ Test Symbol rep
 <body>
 <pre id="test">
 <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;
+  const {
+    REPS,
+    getRep,
+  } = browserRequire("devtools/client/shared/components/reps/reps");
+  let { SymbolRep } = REPS;
 
   let gripStubs = new Map();
   gripStubs.set("testSymbolFoo", {
     type: "symbol",
     name: "foo"
   });
   gripStubs.set("testSymbolWithoutIdentifier", {
     type: "symbol"
   });
 
   try {
     // Test that correct rep is chosen
-    const renderedRep = shallowRenderComponent(
-      Rep,
-      { object: gripStubs.get("testSymbolFoo")}
-    );
-
-    is(renderedRep.type, SymbolRep.rep,
-      `Rep correctly selects ${SymbolRep.rep.displayName}`);
+    is(getRep(gripStubs.get("testSymbolFoo")), SymbolRep.rep,
+      "Rep correctly selects SymbolRep Rep");
 
     // Test rendering
     yield testSymbol();
     yield testSymbolWithoutIdentifier();
   } catch (e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
--- 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
@@ -17,50 +17,59 @@ Test text-node rep
 <script src="head.js" type="application/javascript"></script>
 <script type="application/javascript">
 "use strict";
 
 window.onload = Task.async(function* () {
   const {
     REPS,
     MODE,
+    getRep,
     getSelectableInInspectorGrips,
   } = browserRequire("devtools/client/shared/components/reps/reps");
-  let { Rep, TextNode } = REPS;
+  let { TextNode } = REPS;
 
   let gripStubs = new Map();
   gripStubs.set("testRendering", {
     "class": "Text",
     "actor": "server1.conn1.child1/obj50",
     "preview": {
       "kind": "DOMNode",
       "nodeType": 3,
       "nodeName": "#text",
-      "textContent": "hello world"
+      "textContent": "hello world",
+      "isConnected": true,
+    }
+  });
+  gripStubs.set("testRenderingDisconnected", {
+    "class": "Text",
+    "actor": "server1.conn1.child1/obj50",
+    "preview": {
+      "kind": "DOMNode",
+      "nodeType": 3,
+      "nodeName": "#text",
+      "textContent": "hello world",
+      "isConnected": false,
     }
   });
   gripStubs.set("testRenderingWithEOL", {
     "class": "Text",
     "actor": "server1.conn1.child1/obj50",
     "preview": {
       "kind": "DOMNode",
       "nodeType": 3,
       "nodeName": "#text",
       "textContent": "hello\nworld"
     }
   });
 
   try {
     // Test that correct rep is chosen
-    const renderedRep = shallowRenderComponent(Rep, {
-      object: gripStubs.get("testRendering")
-    });
-
-    is(renderedRep.type, TextNode.rep,
-      `Rep correctly selects ${TextNode.rep.displayName}`);
+    is(getRep(gripStubs.get("testRendering")), TextNode.rep,
+      "Rep correctly selects TextNode Rep");
 
     yield testRendering();
     yield testRenderingWithEOL();
 
     yield testOnMouseOver();
     yield testOnMouseOut();
     yield testOnInspectIconClick();
 
@@ -123,81 +132,74 @@ window.onload = Task.async(function* () 
     testRepRenderModes(modeTests, "testRenderingWithEOL", TextNode, stub);
   }
 
   function testOnMouseOver() {
     const stub = gripStubs.get("testRendering");
 
     const grips = getSelectableInInspectorGrips(stub);
     is(grips.length, 1, "the stub has one text node grip");
-    const attachedActorIds = getStubAttachedActorIds(grips);
 
     let mouseOverValue;
     let onDOMNodeMouseOver = (object) => {
       mouseOverValue = object;
     };
     const renderedComponent = renderComponent(TextNode.rep, {
       object: stub,
       onDOMNodeMouseOver,
-      attachedActorIds,
     });
 
     TestUtils.Simulate.mouseOver(renderedComponent);
     is(mouseOverValue, grips[0], "onDOMNodeMouseOver is called " +
       "with the expected argument when mouseover is fired on the Rep");
   }
 
   function testOnMouseOut() {
     const stub = gripStubs.get("testRendering");
 
     const grips = getSelectableInInspectorGrips(stub);
     is(grips.length, 1, "the stub has one text node grip");
-    const attachedActorIds = getStubAttachedActorIds(grips);
 
     let called = false;
     let onDOMNodeMouseOut = (object) => {
       called = true;
     };
     const renderedComponent = renderComponent(TextNode.rep, {
       object: stub,
       onDOMNodeMouseOut,
-      attachedActorIds,
     });
 
     TestUtils.Simulate.mouseOut(renderedComponent);
     is(called, true, "onDOMNodeMouseOut is called when mouseout is fired on the Rep");
   }
 
   function testOnInspectIconClick() {
     const stub = gripStubs.get("testRendering");
 
     const grips = getSelectableInInspectorGrips(stub);
     is(grips.length, 1, "the stub has one text node grip");
-    const attachedActorIds = getStubAttachedActorIds(grips);
 
     let inspectIconClickedValue = null;
     let inspectIconClickedEvent = null;
 
     let onInspectIconClick = (object, event) => {
       inspectIconClickedValue = object;
       inspectIconClickedEvent = event;
     };
 
     const renderedComponentWithoutInspectIcon = renderComponent(TextNode.rep, {
-      object: stub,
+      object: gripStubs.get("testRenderingDisconnected"),
       onInspectIconClick,
-      attachedActorIds: ["someOtherId"]
     });
     is(renderedComponentWithoutInspectIcon.querySelector(".open-inspector"), null,
-      "There isn't an inspect icon when the actor is not in attachedActorIds");
+      "There isn't an inspect icon when the node is not connected to the DOM tree");
 
     const renderedComponent = renderComponent(TextNode.rep, {
       object: stub,
       onInspectIconClick,
-      attachedActorIds,
     });
 
     const inspectIconNode = renderedComponent.querySelector(".open-inspector");
     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");
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_undefined.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_undefined.html
@@ -15,26 +15,28 @@ Test undefined rep
 <body>
 <pre id="test">
 <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;
+    const {
+      REPS,
+      getRep,
+    } = browserRequire("devtools/client/shared/components/reps/reps");
+    let { Undefined } = REPS;
 
     let gripStub = {
       "type": "undefined"
     };
 
     // Test that correct rep is chosen
-    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
-    is(renderedRep.type, Undefined.rep, `Rep correctly selects ${Undefined.rep.displayName}`);
+    is(getRep(gripStub), Undefined.rep, "Rep correctly selects Undefined Rep");
 
     // Test rendering
     const renderedComponent = renderComponent(Undefined.rep, {});
     is(renderedComponent.className, "objectBox objectBox-undefined", "Undefined rep has expected class names");
     is(renderedComponent.textContent, "undefined", "Undefined rep has expected text content");
   } catch(e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_window.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_window.html
@@ -16,17 +16,21 @@ Test window rep
 <pre id="test">
 <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");
+    const {
+      REPS,
+      MODE,
+      getRep,
+    } = browserRequire("devtools/client/shared/components/reps/reps");
     let { Rep, Window } = REPS;
 
     let gripStub = {
       "type": "object",
       "class": "Window",
       "actor": "server1.conn3.obj198",
       "extensible": true,
       "frozen": false,
@@ -34,21 +38,20 @@ window.onload = Task.async(function* () 
       "ownPropertyLength": 887,
       "preview": {
         "kind": "ObjectWithURL",
         "url": "about:newtab"
       }
     };
 
     // Test that correct rep is chosen
-    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
-    is(renderedRep.type, Window.rep, `Rep correctly selects ${Window.rep.displayName}`);
+    is(getRep(gripStub), Window.rep, "Rep correctly selects Window Rep");
 
     // Test rendering
-    const renderedComponent = renderComponent(Window.rep, { object: gripStub });
+    const renderedComponent = renderComponent(Rep, { object: gripStub });
     ok(renderedComponent.className.includes("objectBox-Window"), "Window rep has expected class name");
     is(renderedComponent.textContent, "Window about:newtab", "Window rep has expected text content");
     const innerNode = renderedComponent.querySelector(".objectPropValue");
     is(innerNode.textContent, "about:newtab", "Window rep has expected inner HTML structure and text content");
 
     const tinyRenderedComponent = renderComponent(Window.rep, {
       object: gripStub,
       mode: MODE.TINY,