Bug 1357341 - Reps 0.6.0: update reps bundle from Github; r=jdescottes draft
authornchevobbe <nchevobbe@mozilla.com>
Thu, 20 Apr 2017 10:43:32 +0200
changeset 565774 b674cd7b3bb493abe1130485b4e3b388b11fdec6
parent 565673 f6be27f5457ffd75b3763db09a139e24bc2155dc
child 565775 c930c3f2fd8a7c71c8347872607389d4321e4171
push id55003
push userbmo:nchevobbe@mozilla.com
push dateThu, 20 Apr 2017 11:53:55 +0000
reviewersjdescottes
bugs1357341
milestone55.0a1
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,