Bug 1436670 - Devtools Reps: update bundle to v0.20.0; r=Honza. draft
authorNicolas Chevobbe <nchevobbe@mozilla.com>
Mon, 19 Feb 2018 11:05:06 +0100
changeset 756936 52aa92d4b15105913d5e873454fc7f5c239fb0ee
parent 756852 ad133cd410a719c0b67e61b8d3b1c77a32fd80a9
push id99584
push userbmo:nchevobbe@mozilla.com
push dateMon, 19 Feb 2018 10:25:22 +0000
reviewersHonza
bugs1436670
milestone60.0a1
Bug 1436670 - Devtools Reps: update bundle to v0.20.0; r=Honza. MozReview-Commit-ID: 75lSnYTx0Ql
devtools/client/shared/components/reps/reps.css
devtools/client/shared/components/reps/reps.js
--- a/devtools/client/shared/components/reps/reps.css
+++ b/devtools/client/shared/components/reps/reps.css
@@ -61,16 +61,22 @@
 .objectBox-textNode,
 .objectBox-string,
 .objectBox-symbol {
   color: var(--string-color);
 }
 
 .objectBox-string a, .objectBox-string a:visited {
   color: currentColor;
+  text-decoration: none;
+  font-style: italic;
+}
+
+.objectBox-string a:hover {
+  text-decoration: underline;
 }
 
 .objectBox-function,
 .objectBox-stackTrace,
 .objectBox-profile {
   color: var(--object-color);
 }
 
@@ -144,26 +150,33 @@
   color: var(--node-color);
 }
 
 .angleBracket {
   color: var(--theme-body-color);
 }
 
 /******************************************************************************/
+/* Length bubble for arraylikes and maplikes */
+
+.objectLengthBubble {
+  color: var(--null-color);
+}
+
+/******************************************************************************/
 
 .objectLeftBrace,
 .objectRightBrace,
 .arrayLeftBracket,
 .arrayRightBracket {
   color: var(--object-color);
 }
 
 /******************************************************************************/
-/* Cycle reference*/
+/* Cycle reference */
 
 .objectBox-Reference {
   font-weight: bold;
   color: var(--reference-color);
 }
 
 [class*="objectBox"] > .objectTitle {
   color: var(--object-color);
--- 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-dom-factories"), require("devtools/client/shared/vendor/lodash"), require("devtools/client/shared/vendor/react-prop-types"), require("devtools/client/shared/vendor/react"));
 	else if(typeof define === 'function' && define.amd)
 		define(["devtools/client/shared/vendor/react-dom-factories", "devtools/client/shared/vendor/lodash", "devtools/client/shared/vendor/react-prop-types", "devtools/client/shared/vendor/react"], factory);
 	else {
 		var a = typeof exports === 'object' ? factory(require("devtools/client/shared/vendor/react-dom-factories"), require("devtools/client/shared/vendor/lodash"), require("devtools/client/shared/vendor/react-prop-types"), require("devtools/client/shared/vendor/react")) : factory(root["devtools/client/shared/vendor/react-dom-factories"], root["devtools/client/shared/vendor/lodash"], root["devtools/client/shared/vendor/react-prop-types"], root["devtools/client/shared/vendor/react"]);
 		for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];
 	}
-})(this, function(__WEBPACK_EXTERNAL_MODULE_1__, __WEBPACK_EXTERNAL_MODULE_56__, __WEBPACK_EXTERNAL_MODULE_2__, __WEBPACK_EXTERNAL_MODULE_7__) {
+})(this, function(__WEBPACK_EXTERNAL_MODULE_1__, __WEBPACK_EXTERNAL_MODULE_56__, __WEBPACK_EXTERNAL_MODULE_2__, __WEBPACK_EXTERNAL_MODULE_8__) {
 return /******/ (function(modules) { // webpackBootstrap
 /******/ 	// The module cache
 /******/ 	var installedModules = {};
 /******/
 /******/ 	// The require function
 /******/ 	function __webpack_require__(moduleId) {
 /******/
 /******/ 		// Check if module is in cache
@@ -65,17 +65,17 @@ return /******/ (function(modules) { // 
 /******/
 /******/ 	// Object.prototype.hasOwnProperty.call
 /******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
 /******/
 /******/ 	// __webpack_public_path__
 /******/ 	__webpack_require__.p = "/assets/build";
 /******/
 /******/ 	// Load entry module and return exports
-/******/ 	return __webpack_require__(__webpack_require__.s = 19);
+/******/ 	return __webpack_require__(__webpack_require__.s = 20);
 /******/ })
 /************************************************************************/
 /******/ ([
 /* 0 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
@@ -455,16 +455,22 @@ function isURL(token) {
     }
     new URL(token);
     return true;
   } catch (e) {
     return false;
   }
 }
 
+const ellipsisElement = span({
+  key: "more",
+  className: "more-ellipsis",
+  title: `more${ELLIPSIS}`
+}, ELLIPSIS);
+
 module.exports = {
   isGrip,
   isURL,
   cropString,
   containsURL,
   rawCropString,
   sanitizeString,
   escapeString,
@@ -473,16 +479,17 @@ module.exports = {
   parseURLParams,
   parseURLEncodedText,
   getFileName,
   getURLDisplayString,
   maybeEscapePropertyName,
   getGripPreviewItems,
   getGripType,
   tokenSplitRegex,
+  ellipsisElement,
   ELLIPSIS
 };
 
 /***/ }),
 /* 1 */
 /***/ (function(module, exports) {
 
 module.exports = __WEBPACK_EXTERNAL_MODULE_1__;
@@ -518,25 +525,24 @@ module.exports = {
 
 "use strict";
 
 
 /* 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/. */
 
-__webpack_require__(20);
+__webpack_require__(21);
 
 // Load all existing rep templates
-const Undefined = __webpack_require__(21);
-const Null = __webpack_require__(22);
-const StringRep = __webpack_require__(6);
-const LongStringRep = __webpack_require__(23);
+const Undefined = __webpack_require__(22);
+const Null = __webpack_require__(23);
+const StringRep = __webpack_require__(7);
 const Number = __webpack_require__(24);
-const ArrayRep = __webpack_require__(10);
+const ArrayRep = __webpack_require__(5);
 const Obj = __webpack_require__(25);
 const SymbolRep = __webpack_require__(26);
 const InfinityRep = __webpack_require__(27);
 const NaNRep = __webpack_require__(28);
 const Accessor = __webpack_require__(29);
 
 // DOM types (grips)
 const Attribute = __webpack_require__(30);
@@ -550,24 +556,24 @@ const StyleSheet = __webpack_require__(4
 const CommentNode = __webpack_require__(41);
 const ElementNode = __webpack_require__(42);
 const TextNode = __webpack_require__(43);
 const ErrorRep = __webpack_require__(44);
 const Window = __webpack_require__(45);
 const ObjectWithText = __webpack_require__(46);
 const ObjectWithURL = __webpack_require__(47);
 const GripArray = __webpack_require__(13);
-const GripMap = __webpack_require__(14);
-const GripMapEntry = __webpack_require__(15);
-const Grip = __webpack_require__(8);
+const GripMap = __webpack_require__(15);
+const GripMapEntry = __webpack_require__(16);
+const Grip = __webpack_require__(9);
 
 // List of all registered template.
 // XXX there should be a way for extensions to register a new
 // or modify an existing rep.
-let reps = [RegExp, StyleSheet, Event, DateTime, CommentNode, ElementNode, TextNode, Attribute, LongStringRep, Func, PromiseRep, ArrayRep, Document, Window, ObjectWithText, ObjectWithURL, ErrorRep, GripArray, GripMap, GripMapEntry, Grip, Undefined, Null, StringRep, Number, SymbolRep, InfinityRep, NaNRep, Accessor];
+let reps = [RegExp, StyleSheet, Event, DateTime, CommentNode, ElementNode, TextNode, Attribute, Func, PromiseRep, ArrayRep, Document, Window, ObjectWithText, ObjectWithURL, ErrorRep, GripArray, GripMap, GripMapEntry, Grip, Undefined, Null, StringRep, Number, SymbolRep, InfinityRep, NaNRep, Accessor];
 
 /**
  * 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 = function (props) {
@@ -625,17 +631,16 @@ module.exports = {
     ErrorRep,
     Event,
     Func,
     Grip,
     GripArray,
     GripMap,
     GripMapEntry,
     InfinityRep,
-    LongStringRep,
     NaNRep,
     Null,
     Number,
     Obj,
     ObjectWithText,
     ObjectWithURL,
     PromiseRep,
     RegExp,
@@ -658,16 +663,163 @@ module.exports = {
 "use strict";
 
 
 /* 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/. */
 
 // Dependencies
+const dom = __webpack_require__(1);
+const PropTypes = __webpack_require__(2);
+const {
+  wrapRender
+} = __webpack_require__(0);
+const { MODE } = __webpack_require__(3);
+const { span } = dom;
+
+const ModePropType = 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]));
+
+/**
+ * Renders an array. The array is enclosed by left and right bracket
+ * and the max number of rendered items depends on the current mode.
+ */
+ArrayRep.propTypes = {
+  mode: ModePropType,
+  object: 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;
+    if (isEmpty) {
+      items = [];
+    } else {
+      items = [span({
+        className: "more-ellipsis",
+        title: "more…"
+      }, "…")];
+    }
+    brackets = needSpace(false);
+  } else {
+    items = arrayIterator(props, object, maxLengthMap.get(mode));
+    brackets = needSpace(items.length > 0);
+  }
+
+  return span({
+    className: "objectBox objectBox-array" }, span({
+    className: "arrayLeftBracket"
+  }, brackets.left), ...items, span({
+    className: "arrayRightBracket"
+  }, brackets.right), span({
+    className: "arrayProperties",
+    role: "group" }));
+}
+
+function arrayIterator(props, array, max) {
+  let items = [];
+
+  for (let i = 0; i < array.length && i < max; i++) {
+    let config = {
+      mode: MODE.TINY,
+      delim: i == array.length - 1 ? "" : ", "
+    };
+    let item;
+
+    try {
+      item = ItemRep(Object.assign({}, props, config, {
+        object: array[i]
+      }));
+    } catch (exc) {
+      item = ItemRep(Object.assign({}, props, config, {
+        object: exc
+      }));
+    }
+    items.push(item);
+  }
+
+  if (array.length > max) {
+    items.push(span({
+      className: "more-ellipsis",
+      title: "more…"
+    }, "…"));
+  }
+
+  return items;
+}
+
+/**
+ * Renders array item. Individual values are separated by a comma.
+ */
+ItemRep.propTypes = {
+  object: PropTypes.any.isRequired,
+  delim: PropTypes.string.isRequired,
+  mode: ModePropType
+};
+
+function ItemRep(props) {
+  const { Rep } = __webpack_require__(4);
+
+  let {
+    object,
+    delim,
+    mode
+  } = props;
+  return span({}, Rep(Object.assign({}, props, {
+    object: object,
+    mode: mode
+  })), delim);
+}
+
+function getLength(object) {
+  return object.length;
+}
+
+function supportsObject(object) {
+  return Array.isArray(object) || Object.prototype.toString.call(object) === "[object Arguments]";
+}
+
+const maxLengthMap = new Map();
+maxLengthMap.set(MODE.SHORT, 3);
+maxLengthMap.set(MODE.LONG, 10);
+
+// Exports from this module
+module.exports = {
+  rep: wrapRender(ArrayRep),
+  supportsObject,
+  maxLengthMap,
+  getLength,
+  ModePropType
+};
+
+/***/ }),
+/* 6 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
+/* 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/. */
+
+// Dependencies
 const PropTypes = __webpack_require__(2);
 const {
   maybeEscapePropertyName,
   wrapRender
 } = __webpack_require__(0);
 const { MODE } = __webpack_require__(3);
 
 const dom = __webpack_require__(1);
@@ -697,17 +849,17 @@ PropRep.propTypes = {
 /**
  * Function that given a name, a delimiter and an object returns an array
  * of React elements representing an object property (e.g. `name: value`)
  *
  * @param {Object} props
  * @return {Array} Array of React elements.
  */
 function PropRep(props) {
-  const Grip = __webpack_require__(8);
+  const Grip = __webpack_require__(9);
   const { Rep } = __webpack_require__(4);
 
   let {
     name,
     mode,
     equal,
     suppressQuotes
   } = props;
@@ -733,17 +885,17 @@ function PropRep(props) {
     "className": "objectEqual"
   }, equal), Rep(Object.assign({}, props))];
 }
 
 // Exports from this module
 module.exports = wrapRender(PropRep);
 
 /***/ }),
-/* 6 */
+/* 7 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/. */
@@ -754,75 +906,154 @@ const PropTypes = __webpack_require__(2)
 const {
   containsURL,
   isURL,
   escapeString,
   getGripType,
   rawCropString,
   sanitizeString,
   wrapRender,
+  isGrip,
   tokenSplitRegex,
   ELLIPSIS
 } = __webpack_require__(0);
 
 const dom = __webpack_require__(1);
 const { a, span } = dom;
 
 /**
  * Renders a string. String value is enclosed within quotes.
  */
 StringRep.propTypes = {
   useQuotes: PropTypes.bool,
   escapeWhitespace: PropTypes.bool,
   style: PropTypes.object,
-  object: PropTypes.string.isRequired,
-  member: PropTypes.any,
-  cropLimit: PropTypes.number,
+  cropLimit: PropTypes.number.isRequired,
+  member: PropTypes.string,
+  object: PropTypes.object.isRequired,
   openLink: PropTypes.func,
   className: PropTypes.string,
   omitLinkHref: PropTypes.bool
 };
 
 function StringRep(props) {
   let {
     className,
+    style,
     cropLimit,
-    object: text,
-    member,
-    style,
+    object,
     useQuotes = true,
     escapeWhitespace = true,
+    member,
     openLink,
     omitLinkHref = true
   } = props;
 
+  let text = object;
+
+  const isLong = isLongString(object);
+  const shouldCrop = (!member || !member.open) && cropLimit && text.length > cropLimit;
+
+  if (isLong) {
+    text = maybeCropLongString({
+      shouldCrop,
+      cropLimit
+    }, text);
+  }
+
+  text = formatText({
+    useQuotes,
+    escapeWhitespace
+  }, text);
+
+  const config = getElementConfig({
+    className,
+    style,
+    actor: object.actor
+  });
+
+  if (!isLong) {
+    if (containsURL(text)) {
+      return span(config, ...getLinkifiedElements(text, shouldCrop && cropLimit, omitLinkHref, openLink));
+    }
+
+    // Cropping of longString has been handled before formatting.
+    text = maybeCropString({
+      isLong,
+      shouldCrop,
+      cropLimit
+    }, text);
+  }
+
+  return span(config, text);
+}
+
+function maybeCropLongString(opts, text) {
+  const {
+    shouldCrop,
+    cropLimit
+  } = opts;
+
+  const {
+    fullText,
+    initial,
+    length
+  } = text;
+
+  text = shouldCrop ? initial.substring(0, cropLimit) : fullText || initial;
+
+  if (text.length < length) {
+    text += ELLIPSIS;
+  }
+
+  return text;
+}
+
+function formatText(opts, text) {
+  let {
+    useQuotes,
+    escapeWhitespace
+  } = opts;
+
+  return useQuotes ? escapeString(text, escapeWhitespace) : sanitizeString(text);
+}
+
+function getElementConfig(opts) {
+  const {
+    className,
+    style,
+    actor
+  } = opts;
+
+  const config = {};
+
+  if (actor) {
+    config["data-link-actor-id"] = actor;
+  }
+
   const classNames = ["objectBox", "objectBox-string"];
   if (className) {
     classNames.push(className);
   }
-  let config = { className: classNames.join(" ") };
+  config.className = classNames.join(" ");
+
   if (style) {
     config.style = style;
   }
 
-  if (useQuotes) {
-    text = escapeString(text, escapeWhitespace);
-  } else {
-    text = sanitizeString(text);
-  }
-
-  const shouldCrop = (!member || !member.open) && cropLimit && text.length > cropLimit;
-  if (!containsURL(text)) {
-    if (shouldCrop) {
-      text = rawCropString(text, cropLimit);
-    }
-    return span(config, text);
-  }
-
-  return span(config, ...getLinkifiedElements(text, shouldCrop && cropLimit, omitLinkHref, openLink));
+  return config;
+}
+
+function maybeCropString(opts, text) {
+  const {
+    shouldCrop,
+    cropLimit
+  } = opts;
+
+  return shouldCrop ? rawCropString(text, cropLimit) : text;
 }
 
 /**
  * Get an array of the elements representing the string, cropped if needed,
  * with actual links.
  *
  * @param {String} text: The actual string to linkify.
  * @param {Integer | null} cropLimit
@@ -921,35 +1152,43 @@ function getCroppedString(text, offset =
     // The string should be cropped at the beginning.
     const cutIndex = endCropIndex - start;
     return text.substring(cutIndex);
   }
 
   return text;
 }
 
+function isLongString(object) {
+  return object && object.type === "longString";
+}
+
 function supportsObject(object, noGrip = false) {
+  if (noGrip === false && isGrip(object)) {
+    return isLongString(object);
+  }
+
   return getGripType(object, noGrip) == "string";
 }
 
 // Exports from this module
 
 module.exports = {
   rep: wrapRender(StringRep),
   supportsObject
 };
 
 /***/ }),
-/* 7 */
+/* 8 */
 /***/ (function(module, exports) {
 
-module.exports = __WEBPACK_EXTERNAL_MODULE_7__;
+module.exports = __WEBPACK_EXTERNAL_MODULE_8__;
 
 /***/ }),
-/* 8 */
+/* 9 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/. */
@@ -957,17 +1196,17 @@ module.exports = __WEBPACK_EXTERNAL_MODU
 // ReactJS
 const PropTypes = __webpack_require__(2);
 
 // Dependencies
 const {
   isGrip,
   wrapRender
 } = __webpack_require__(0);
-const PropRep = __webpack_require__(5);
+const PropRep = __webpack_require__(6);
 const { MODE } = __webpack_require__(3);
 
 const dom = __webpack_require__(1);
 const { span } = dom;
 
 /**
  * Renders generic grip. Grip is client representation
  * of remote JS object and is used as an input object
@@ -1239,16 +1478,20 @@ function getPropValue(property) {
 }
 
 // Registration
 function supportsObject(object, noGrip = false) {
   if (noGrip === true || !isGrip(object)) {
     return false;
   }
 
+  if (object.class === "DeadObject") {
+    return true;
+  }
+
   return object.preview ? typeof object.preview.ownProperties !== "undefined" : typeof object.ownPropertyLength !== "undefined";
 }
 
 const maxLengthMap = new Map();
 maxLengthMap.set(MODE.SHORT, 3);
 maxLengthMap.set(MODE.LONG, 10);
 
 // Grip is used in propIterator and has to be defined here.
@@ -1257,33 +1500,33 @@ let Grip = {
   supportsObject,
   maxLengthMap
 };
 
 // Exports from this module
 module.exports = Grip;
 
 /***/ }),
-/* 9 */
+/* 10 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 var _svgInlineReact = __webpack_require__(11);
 
 var _svgInlineReact2 = _interopRequireDefault(_svgInlineReact);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 /* 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/. */
 
-const React = __webpack_require__(7);
+const React = __webpack_require__(8);
 const PropTypes = __webpack_require__(2);
 
 
 const svg = {
   "open-inspector": __webpack_require__(36),
   "jump-definition": __webpack_require__(37)
 };
 
@@ -1304,187 +1547,41 @@ function Svg(name, props) {
   }
   props = Object.assign({}, props, { className, src: svg[name] });
   return React.createElement(_svgInlineReact2.default, props);
 }
 
 module.exports = Svg;
 
 /***/ }),
-/* 10 */
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-/* 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/. */
-
-// Dependencies
-const dom = __webpack_require__(1);
-const PropTypes = __webpack_require__(2);
-const {
-  wrapRender
-} = __webpack_require__(0);
-const { MODE } = __webpack_require__(3);
-const { span } = dom;
-
-const ModePropType = 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]));
-
-/**
- * Renders an array. The array is enclosed by left and right bracket
- * and the max number of rendered items depends on the current mode.
- */
-ArrayRep.propTypes = {
-  mode: ModePropType,
-  object: 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;
-    if (isEmpty) {
-      items = [];
-    } else {
-      items = [span({
-        className: "more-ellipsis",
-        title: "more…"
-      }, "…")];
-    }
-    brackets = needSpace(false);
-  } else {
-    items = arrayIterator(props, object, maxLengthMap.get(mode));
-    brackets = needSpace(items.length > 0);
-  }
-
-  return span({
-    className: "objectBox objectBox-array" }, span({
-    className: "arrayLeftBracket"
-  }, brackets.left), ...items, span({
-    className: "arrayRightBracket"
-  }, brackets.right), span({
-    className: "arrayProperties",
-    role: "group" }));
-}
-
-function arrayIterator(props, array, max) {
-  let items = [];
-
-  for (let i = 0; i < array.length && i < max; i++) {
-    let config = {
-      mode: MODE.TINY,
-      delim: i == array.length - 1 ? "" : ", "
-    };
-    let item;
-
-    try {
-      item = ItemRep(Object.assign({}, props, config, {
-        object: array[i]
-      }));
-    } catch (exc) {
-      item = ItemRep(Object.assign({}, props, config, {
-        object: exc
-      }));
-    }
-    items.push(item);
-  }
-
-  if (array.length > max) {
-    items.push(span({
-      className: "more-ellipsis",
-      title: "more…"
-    }, "…"));
-  }
-
-  return items;
-}
-
-/**
- * Renders array item. Individual values are separated by a comma.
- */
-ItemRep.propTypes = {
-  object: PropTypes.any.isRequired,
-  delim: PropTypes.string.isRequired,
-  mode: ModePropType
-};
-
-function ItemRep(props) {
-  const { Rep } = __webpack_require__(4);
-
-  let {
-    object,
-    delim,
-    mode
-  } = props;
-  return span({}, Rep(Object.assign({}, props, {
-    object: object,
-    mode: mode
-  })), delim);
-}
-
-function getLength(object) {
-  return object.length;
-}
-
-function supportsObject(object) {
-  return Array.isArray(object) || Object.prototype.toString.call(object) === "[object Arguments]";
-}
-
-const maxLengthMap = new Map();
-maxLengthMap.set(MODE.SHORT, 3);
-maxLengthMap.set(MODE.LONG, 10);
-
-// Exports from this module
-module.exports = {
-  rep: wrapRender(ArrayRep),
-  supportsObject,
-  maxLengthMap,
-  getLength
-};
-
-/***/ }),
 /* 11 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
     value: true
 });
 
 var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
 
 var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
 
-var _react = __webpack_require__(7);
+var _react = __webpack_require__(8);
 
 var _react2 = _interopRequireDefault(_react);
 
 var _propTypes = __webpack_require__(2);
 
 var _util = __webpack_require__(35);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
-function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.includes(i)) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
+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 _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
 
 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 process = process || { env: {} };
@@ -1594,34 +1691,38 @@ 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/. */
 
 // Dependencies
 const PropTypes = __webpack_require__(2);
+
+const { lengthBubble } = __webpack_require__(14);
 const {
   getGripType,
   isGrip,
-  wrapRender
+  wrapRender,
+  ellipsisElement
 } = __webpack_require__(0);
 const { MODE } = __webpack_require__(3);
 
 const dom = __webpack_require__(1);
 const { span } = dom;
+const { ModePropType } = __webpack_require__(5);
 
 /**
  * Renders an array. The array is enclosed by left and right bracket
  * and the max number of rendered items depends on the current mode.
  */
 GripArray.propTypes = {
   object: PropTypes.object.isRequired,
   // @TODO Change this to Object.values once it's supported in Node's version of V8
-  mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
+  mode: ModePropType,
   provider: PropTypes.object,
   onDOMNodeMouseOver: PropTypes.func,
   onDOMNodeMouseOut: PropTypes.func,
   onInspectIconClick: PropTypes.func
 };
 
 function GripArray(props) {
   let {
@@ -1630,46 +1731,43 @@ function GripArray(props) {
   } = props;
 
   let items;
   let brackets;
   let needSpace = function (space) {
     return space ? { left: "[ ", right: " ]" } : { left: "[", right: "]" };
   };
 
+  const config = {
+    "data-link-actor-id": object.actor,
+    className: "objectBox objectBox-array"
+  };
+
+  const title = getTitle(props, object);
+
   if (mode === MODE.TINY) {
-    let objectLength = getLength(object);
-    let isEmpty = objectLength === 0;
-    let ellipsis;
-    if (!isEmpty) {
-      ellipsis = span({
-        className: "more-ellipsis",
-        title: "more…"
-      }, "…");
+    const isEmpty = getLength(object) === 0;
+
+    // Omit bracketed ellipsis for non-empty non-Array arraylikes (f.e: Sets).
+    if (!isEmpty && object.class !== "Array") {
+      return span(config, title);
     }
 
-    let title;
-    if (object.class != "Array") {
-      title = object.class + " ";
-    }
     brackets = needSpace(false);
-    return span({
-      "data-link-actor-id": object.actor,
-      className: "objectBox objectBox-array" }, title, span({
+    return span(config, title, span({
       className: "arrayLeftBracket"
-    }, brackets.left), ellipsis, span({
+    }, brackets.left), isEmpty ? null : ellipsisElement, span({
       className: "arrayRightBracket"
     }, brackets.right));
   }
+
   let max = maxLengthMap.get(mode);
   items = arrayIterator(props, object, max);
   brackets = needSpace(items.length > 0);
 
-  let title = getTitle(props, object);
-
   return span({
     "data-link-actor-id": object.actor,
     className: "objectBox objectBox-array" }, title, span({
     className: "arrayLeftBracket"
   }, brackets.left), ...interleaveCommas(items), span({
     className: "arrayRightBracket"
   }, brackets.right), span({
     className: "arrayProperties",
@@ -1689,24 +1787,46 @@ function getLength(grip) {
   if (!grip.preview) {
     return 0;
   }
 
   return grip.preview.length || grip.preview.childNodesLength || 0;
 }
 
 function getTitle(props, object) {
-  if (props.mode === MODE.TINY) {
-    return "";
-  }
+  let objectLength = getLength(object);
+  let isEmpty = objectLength === 0;
 
   let title = props.title || object.class || "Array";
+
+  const length = lengthBubble({
+    object,
+    mode: props.mode,
+    maxLengthMap,
+    getLength
+  });
+
+  if (props.mode === MODE.TINY) {
+    if (isEmpty) {
+      return object.class === "Array" ? "" : span({
+        className: "objectTitle" }, title, " ");
+    }
+
+    let trailingSpace;
+    if (object.class === "Array") {
+      title = "";
+      trailingSpace = " ";
+    }
+
+    return span({
+      className: "objectTitle" }, title, length, trailingSpace);
+  }
+
   return span({
-    className: "objectTitle"
-  }, title + " ");
+    className: "objectTitle" }, title, length, " ");
 }
 
 function getPreviewItems(grip) {
   if (!grip.preview) {
     return null;
   }
 
   return grip.preview.items || grip.preview.childNodes || [];
@@ -1765,20 +1885,17 @@ function arrayIterator(props, grip, max)
   // Handle trailing empty slots if there are some.
   if (items.length < max && emptySlots > 0) {
     items.push(getEmptySlotsElement(emptySlots));
     foldedEmptySlots = foldedEmptySlots + emptySlots - 1;
   }
 
   const itemsShown = items.length + foldedEmptySlots;
   if (gripLength > itemsShown) {
-    items.push(span({
-      className: "more-ellipsis",
-      title: "more…"
-    }, "…"));
+    items.push(ellipsisElement);
   }
 
   return items;
 }
 
 function getEmptySlotsElement(number) {
   // TODO: Use l10N - See https://github.com/devtools-html/reps/issues/141
   return `<${number} empty slot${number > 1 ? "s" : ""}>`;
@@ -1806,40 +1923,94 @@ module.exports = {
 
 /***/ }),
 /* 14 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
+const PropTypes = __webpack_require__(2);
+
+const { wrapRender } = __webpack_require__(0);
+const { MODE } = __webpack_require__(3);
+const { ModePropType } = __webpack_require__(5);
+
+const dom = __webpack_require__(1);
+const { span } = dom;
+
+GripLengthBubble.propTypes = {
+  object: PropTypes.object.isRequired,
+  maxLengthMap: PropTypes.instanceOf(Map).isRequired,
+  getLength: PropTypes.func.isRequired,
+  mode: ModePropType,
+  visibilityThreshold: PropTypes.number
+};
+
+function GripLengthBubble(props) {
+  const {
+    object,
+    mode = MODE.SHORT,
+    visibilityThreshold = 2,
+    maxLengthMap,
+    getLength,
+    showZeroLength = false
+  } = props;
+
+  const length = getLength(object);
+  const isEmpty = length === 0;
+  const isObvious = [MODE.SHORT, MODE.LONG].includes(mode) && length > 0 && length <= maxLengthMap.get(mode) && length <= visibilityThreshold;
+  if (isEmpty && !showZeroLength || isObvious) {
+    return "";
+  }
+
+  return span({
+    className: "objectLengthBubble"
+  }, `(${length})`);
+}
+
+module.exports = {
+  lengthBubble: wrapRender(GripLengthBubble)
+};
+
+/***/ }),
+/* 15 */
+/***/ (function(module, exports, __webpack_require__) {
+
+"use strict";
+
+
 /* 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/. */
 
 // Dependencies
+
+const { lengthBubble } = __webpack_require__(14);
 const PropTypes = __webpack_require__(2);
 const {
   isGrip,
-  wrapRender
+  wrapRender,
+  ellipsisElement
 } = __webpack_require__(0);
-const PropRep = __webpack_require__(5);
+const PropRep = __webpack_require__(6);
 const { MODE } = __webpack_require__(3);
+const { ModePropType } = __webpack_require__(5);
 
 const dom = __webpack_require__(1);
 const { span } = dom;
 
 /**
  * Renders an map. A map is represented by a list of its
  * entries enclosed in curly brackets.
  */
 GripMap.propTypes = {
   object: PropTypes.object,
   // @TODO Change this to Object.values once it's supported in Node's version of V8
-  mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
+  mode: ModePropType,
   isInterestingEntry: PropTypes.func,
   onDOMNodeMouseOver: PropTypes.func,
   onDOMNodeMouseOut: PropTypes.func,
   onInspectIconClick: PropTypes.func,
   title: PropTypes.string
 };
 
 function GripMap(props) {
@@ -1848,34 +2019,42 @@ function GripMap(props) {
     object
   } = props;
 
   const config = {
     "data-link-actor-id": object.actor,
     className: "objectBox objectBox-object"
   };
 
-  if (mode === MODE.TINY) {
-    return span(config, getTitle(props, object));
-  }
-
-  let propsArray = safeEntriesIterator(props, object, maxLengthMap.get(mode));
-
-  return span(config, getTitle(props, object), span({
+  const title = getTitle(props, object);
+  const isEmpty = getLength(object) === 0;
+
+  if (isEmpty || mode === MODE.TINY) {
+    return span(config, title);
+  }
+
+  const propsArray = safeEntriesIterator(props, object, maxLengthMap.get(mode));
+
+  return span(config, title, span({
     className: "objectLeftBrace"
   }, " { "), ...propsArray, span({
     className: "objectRightBrace"
   }, " }"));
 }
 
 function getTitle(props, object) {
-  let title = props.title || (object && object.class ? object.class : "Map");
+  const title = props.title || (object && object.class ? object.class : "Map");
   return span({
-    className: "objectTitle"
-  }, title);
+    className: "objectTitle" }, title, lengthBubble({
+    object,
+    mode: props.mode,
+    maxLengthMap,
+    getLength,
+    showZeroLength: true
+  }));
 }
 
 function safeEntriesIterator(props, object, max) {
   max = typeof max === "undefined" ? 3 : max;
   try {
     return entriesIterator(props, object, max);
   } catch (err) {
     console.error(err);
@@ -1897,21 +2076,17 @@ function entriesIterator(props, object, 
     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 < getLength(object)) {
     // There are some undisplayed entries. Then display "…".
-    entries.push(span({
-      key: "more",
-      className: "more-ellipsis",
-      title: "more…"
-    }, "…"));
+    entries.push(ellipsisElement);
   }
 
   return unfoldEntries(entries);
 }
 
 function unfoldEntries(items) {
   return items.reduce((res, item, index) => {
     if (Array.isArray(item)) {
@@ -2008,17 +2183,17 @@ maxLengthMap.set(MODE.LONG, 10);
 module.exports = {
   rep: wrapRender(GripMap),
   supportsObject,
   maxLengthMap,
   getLength
 };
 
 /***/ }),
-/* 15 */
+/* 16 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/. */
@@ -2026,17 +2201,17 @@ module.exports = {
 // Dependencies
 const PropTypes = __webpack_require__(2);
 // Shortcuts
 const dom = __webpack_require__(1);
 const { span } = dom;
 const {
   wrapRender
 } = __webpack_require__(0);
-const PropRep = __webpack_require__(5);
+const PropRep = __webpack_require__(6);
 const { MODE } = __webpack_require__(3);
 /**
  * Renders an map entry. A map entry is represented by its key, a column and its value.
  */
 GripMapEntry.propTypes = {
   object: PropTypes.object,
   // @TODO Change this to Object.values once it's supported in Node's version of V8
   mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
@@ -2086,38 +2261,38 @@ function createGripMapEntry(key, value) 
 // Exports from this module
 module.exports = {
   rep: wrapRender(GripMapEntry),
   createGripMapEntry,
   supportsObject
 };
 
 /***/ }),
-/* 16 */
+/* 17 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/. */
 
-const client = __webpack_require__(17);
+const client = __webpack_require__(18);
 const loadProperties = __webpack_require__(55);
-const node = __webpack_require__(18);
+const node = __webpack_require__(19);
 
 module.exports = {
   client,
   loadProperties,
   node
 };
 
 /***/ }),
-/* 17 */
+/* 18 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 async function enumIndexedProperties(objectClient, start, end) {
   try {
     const { iterator } = await objectClient.enumProperties({ ignoreNonIndexedProperties: true });
@@ -2182,32 +2357,32 @@ module.exports = {
   enumEntries,
   enumIndexedProperties,
   enumNonIndexedProperties,
   enumSymbols,
   getPrototype
 };
 
 /***/ }),
-/* 18 */
+/* 19 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/. */
 
 const { get, has } = __webpack_require__(56);
 const { maybeEscapePropertyName } = __webpack_require__(0);
-const ArrayRep = __webpack_require__(10);
+const ArrayRep = __webpack_require__(5);
 const GripArrayRep = __webpack_require__(13);
-const GripMap = __webpack_require__(14);
-const GripMapEntryRep = __webpack_require__(15);
+const GripMap = __webpack_require__(15);
+const GripMapEntryRep = __webpack_require__(16);
 
 const MAX_NUMERICAL_PROPERTIES = 100;
 
 const NODE_TYPES = {
   BUCKET: Symbol("[n…n]"),
   DEFAULT_PROPERTIES: Symbol("[default properties]"),
   ENTRIES: Symbol("<entries>"),
   GET: Symbol("<get>"),
@@ -2282,16 +2457,37 @@ function nodeIsFunction(item) {
   return value && value.class === "Function";
 }
 
 function nodeIsOptimizedOut(item) {
   const value = getValue(item);
   return !nodeHasChildren(item) && value && value.optimizedOut;
 }
 
+function nodeIsUninitializedBinding(item) {
+  const value = getValue(item);
+  return value && value.uninitialized;
+}
+
+// Used to check if an item represents a binding that exists in a sourcemap's
+// original file content, but does not match up with a binding found in the
+// generated code.
+function nodeIsUnmappedBinding(item) {
+  const value = getValue(item);
+  return value && value.unmapped;
+}
+
+// Used to check if an item represents a binding that exists in the debugger's
+// parser result, but does not match up with a binding returned by the
+// debugger server.
+function nodeIsUnscopedBinding(item) {
+  const value = getValue(item);
+  return value && value.unscoped;
+}
+
 function nodeIsMissingArguments(item) {
   const value = getValue(item);
   return !nodeHasChildren(item) && value && value.missingArguments;
 }
 
 function nodeHasProperties(item) {
   return !nodeHasChildren(item) && nodeIsObject(item);
 }
@@ -2806,41 +3002,44 @@ module.exports = {
   nodeIsMissingArguments,
   nodeIsObject,
   nodeIsOptimizedOut,
   nodeIsPrimitive,
   nodeIsPromise,
   nodeIsPrototype,
   nodeIsProxy,
   nodeIsSetter,
+  nodeIsUninitializedBinding,
+  nodeIsUnmappedBinding,
+  nodeIsUnscopedBinding,
   nodeIsWindow,
   nodeNeedsNumericalBuckets,
   nodeSupportsNumericalBucketing,
   setNodeChildren,
   sortProperties,
   NODE_TYPES,
   // Export for testing purpose.
   SAFE_PATH_PREFIX
 };
 
 /***/ }),
-/* 19 */
+/* 20 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/. */
 
 const { MODE } = __webpack_require__(3);
 const { REPS, getRep } = __webpack_require__(4);
 const ObjectInspector = __webpack_require__(48);
-const ObjectInspectorUtils = __webpack_require__(16);
+const ObjectInspectorUtils = __webpack_require__(17);
 
 const {
   parseURLEncodedText,
   parseURLParams,
   maybeEscapePropertyName,
   getGripPreviewItems
 } = __webpack_require__(0);
 
@@ -2852,23 +3051,23 @@ module.exports = {
   parseURLEncodedText,
   parseURLParams,
   getGripPreviewItems,
   ObjectInspector,
   ObjectInspectorUtils
 };
 
 /***/ }),
-/* 20 */
+/* 21 */
 /***/ (function(module, exports) {
 
 // removed by extract-text-webpack-plugin
 
 /***/ }),
-/* 21 */
+/* 22 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/. */
@@ -2900,17 +3099,17 @@ function supportsObject(object, noGrip =
 // Exports from this module
 
 module.exports = {
   rep: wrapRender(Undefined),
   supportsObject
 };
 
 /***/ }),
-/* 22 */
+/* 23 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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/. */
@@ -2942,93 +3141,16 @@ function supportsObject(object, noGrip =
 // Exports from this module
 
 module.exports = {
   rep: wrapRender(Null),
   supportsObject
 };
 
 /***/ }),
-/* 23 */
-/***/ (function(module, exports, __webpack_require__) {
-
-"use strict";
-
-
-/* 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/. */
-
-// Dependencies
-const PropTypes = __webpack_require__(2);
-const {
-  escapeString,
-  sanitizeString,
-  isGrip,
-  wrapRender
-} = __webpack_require__(0);
-
-const dom = __webpack_require__(1);
-const { span } = dom;
-
-/**
- * Renders a long string grip.
- */
-LongStringRep.propTypes = {
-  useQuotes: PropTypes.bool,
-  escapeWhitespace: PropTypes.bool,
-  style: PropTypes.object,
-  cropLimit: PropTypes.number.isRequired,
-  member: PropTypes.string,
-  object: PropTypes.object.isRequired
-};
-
-function LongStringRep(props) {
-  let {
-    cropLimit,
-    member,
-    object,
-    style,
-    useQuotes = true,
-    escapeWhitespace = true
-  } = props;
-  let { fullText, initial, length } = object;
-
-  let config = {
-    "data-link-actor-id": object.actor,
-    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, noGrip = false) {
-  if (noGrip === true || !isGrip(object)) {
-    return false;
-  }
-  return object.type === "longString";
-}
-
-// Exports from this module
-module.exports = {
-  rep: wrapRender(LongStringRep),
-  supportsObject
-};
-
-/***/ }),
 /* 24 */
 /***/ (function(module, exports, __webpack_require__) {
 
 "use strict";
 
 
 /* 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
@@ -3084,19 +3206,20 @@ 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/. */
 
 // Dependencies
 const PropTypes = __webpack_require__(2);
 const {
-  wrapRender
+  wrapRender,
+  ellipsisElement
 } = __webpack_require__(0);
-const PropRep = __webpack_require__(5);
+const PropRep = __webpack_require__(6);
 const { MODE } = __webpack_require__(3);
 
 const dom = __webpack_require__(1);
 const { span } = dom;
 
 const DEFAULT_TITLE = "Object";
 
 /**
@@ -3116,21 +3239,17 @@ function ObjectRep(props) {
 
   if (props.mode === MODE.TINY) {
     const tinyModeItems = [];
     if (getTitle(props, object) !== DEFAULT_TITLE) {
       tinyModeItems.push(getTitleElement(props, object));
     } else {
       tinyModeItems.push(span({
         className: "objectLeftBrace"
-      }, "{"), propsArray.length > 0 ? span({
-        key: "more",
-        className: "more-ellipsis",
-        title: "more…"
-      }, "…") : null, span({
+      }, "{"), propsArray.length > 0 ? ellipsisElement : null, span({
         className: "objectRightBrace"
       }, "}"));
     }
 
     return span({ className: "objectBox objectBox-object" }, ...tinyModeItems);
   }
 
   return span({ className: "objectBox objectBox-object" }, getTitleElement(props, object), span({
@@ -3140,17 +3259,17 @@ function ObjectRep(props) {
   }, " }"));
 }
 
 function getTitleElement(props, object) {
   return span({ className: "objectTitle" }, getTitle(props, object));
 }
 
 function getTitle(props, object) {
-  return props.title || object.class || DEFAULT_TITLE;
+  return props.title || DEFAULT_TITLE;
 }
 
 function safePropIterator(props, object, max) {
   max = typeof max === "undefined" ? 3 : max;
   try {
     return propIterator(props, object, max);
   } catch (err) {
     console.error(err);
@@ -3223,21 +3342,17 @@ function propIterator(props, object, max
         continue;
       }
 
       pushPropRep(name, value);
     }
   }
 
   if (propertiesNumber < propertiesNames.length) {
-    elements.push(span({
-      key: "more",
-      className: "more-ellipsis",
-      title: ", more…"
-    }, "…"));
+    elements.push(ellipsisElement);
   }
 
   return elements;
 }
 
 function isInterestingProp(value) {
   const type = typeof value;
   return type == "boolean" || type == "number" || type == "string" && value;
@@ -3477,17 +3592,17 @@ const dom = __webpack_require__(1);
 const { span } = dom;
 
 // Reps
 const {
   getGripType,
   isGrip,
   wrapRender
 } = __webpack_require__(0);
-const { rep: StringRep } = __webpack_require__(6);
+const { rep: StringRep } = __webpack_require__(7);
 
 /**
  * Renders DOM attribute
  */
 Attribute.propTypes = {
   object: PropTypes.object.isRequired
 };
 
@@ -3641,17 +3756,18 @@ function getTitle(grip) {
 }
 
 // Registration
 function supportsObject(object, noGrip = false) {
   if (noGrip === true || !isGrip(object)) {
     return false;
   }
 
-  return object.preview && getGripType(object, noGrip) == "HTMLDocument";
+  const type = getGripType(object, noGrip);
+  return object.preview && (type === "HTMLDocument" || type === "XULDocument");
 }
 
 // Exports from this module
 module.exports = {
   rep: wrapRender(Document),
   supportsObject
 };
 
@@ -3671,17 +3787,17 @@ const PropTypes = __webpack_require__(2)
 
 // Reps
 const {
   isGrip,
   wrapRender
 } = __webpack_require__(0);
 
 const { MODE } = __webpack_require__(3);
-const { rep } = __webpack_require__(8);
+const { rep } = __webpack_require__(9);
 
 /**
  * Renders DOM event objects.
  */
 Event.propTypes = {
   object: PropTypes.object.isRequired,
   // @TODO Change this to Object.values once it's supported in Node's version of V8
   mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
@@ -3779,17 +3895,17 @@ const PropTypes = __webpack_require__(2)
 // Reps
 const {
   getGripType,
   isGrip,
   cropString,
   wrapRender
 } = __webpack_require__(0);
 const { MODE } = __webpack_require__(3);
-const Svg = __webpack_require__(9);
+const Svg = __webpack_require__(10);
 
 const dom = __webpack_require__(1);
 const { span } = dom;
 
 const IGNORED_SOURCE_URLS = ["debugger eval code"];
 
 /**
  * This component represents a template for Function objects.
@@ -4005,17 +4121,17 @@ module.exports = "<!-- This Source Code 
 const PropTypes = __webpack_require__(2);
 // Dependencies
 const {
   getGripType,
   isGrip,
   wrapRender
 } = __webpack_require__(0);
 
-const PropRep = __webpack_require__(5);
+const PropRep = __webpack_require__(6);
 const { MODE } = __webpack_require__(3);
 
 const dom = __webpack_require__(1);
 const { span } = dom;
 
 /**
  * Renders a DOM Promise object.
  */
@@ -4307,20 +4423,20 @@ module.exports = {
 // ReactJS
 const PropTypes = __webpack_require__(2);
 
 // Utils
 const {
   isGrip,
   wrapRender
 } = __webpack_require__(0);
-const { rep: StringRep } = __webpack_require__(6);
+const { rep: StringRep } = __webpack_require__(7);
 const { MODE } = __webpack_require__(3);
 const nodeConstants = __webpack_require__(12);
-const Svg = __webpack_require__(9);
+const Svg = __webpack_require__(10);
 
 const dom = __webpack_require__(1);
 const { span } = dom;
 
 /**
  * Renders DOM element node.
  */
 ElementNode.propTypes = {
@@ -4441,17 +4557,17 @@ const PropTypes = __webpack_require__(2)
 
 // Reps
 const {
   isGrip,
   cropString,
   wrapRender
 } = __webpack_require__(0);
 const { MODE } = __webpack_require__(3);
-const Svg = __webpack_require__(9);
+const Svg = __webpack_require__(10);
 
 const dom = __webpack_require__(1);
 const { span } = dom;
 
 /**
  * Renders DOM #text node.
  */
 TextNode.propTypes = {
@@ -4711,17 +4827,17 @@ module.exports = {
 const PropTypes = __webpack_require__(2);
 
 // Reps
 const {
   isGrip,
   wrapRender
 } = __webpack_require__(0);
 
-const String = __webpack_require__(6).rep;
+const String = __webpack_require__(7).rep;
 
 const dom = __webpack_require__(1);
 const { span } = dom;
 
 /**
  * Renders a grip object with textual data.
  */
 ObjectWithText.propTypes = {
@@ -4839,17 +4955,17 @@ var _devtoolsComponents = __webpack_requ
 var _devtoolsComponents2 = _interopRequireDefault(_devtoolsComponents);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
 /* 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/. */
 
-const { Component, createFactory } = __webpack_require__(7);
+const { Component, createFactory } = __webpack_require__(8);
 const PropTypes = __webpack_require__(2);
 const dom = __webpack_require__(1);
 
 const Tree = createFactory(_devtoolsComponents2.default.Tree);
 __webpack_require__(53);
 
 const classnames = __webpack_require__(54);
 
@@ -4858,17 +4974,17 @@ const {
     Rep,
     Grip
   }
 } = __webpack_require__(4);
 const {
   MODE
 } = __webpack_require__(3);
 
-const Utils = __webpack_require__(16);
+const Utils = __webpack_require__(17);
 
 const {
   getChildren,
   getClosestGripNode,
   getParent,
   getValue,
   nodeHasAccessors,
   nodeHasProperties,
@@ -4876,16 +4992,19 @@ const {
   nodeIsFunction,
   nodeIsGetter,
   nodeIsMapEntry,
   nodeIsMissingArguments,
   nodeIsOptimizedOut,
   nodeIsPrimitive,
   nodeIsPrototype,
   nodeIsSetter,
+  nodeIsUninitializedBinding,
+  nodeIsUnmappedBinding,
+  nodeIsUnscopedBinding,
   nodeIsWindow
 } = Utils.node;
 
 const {
   loadItemProperties
 } = Utils.loadProperties;
 
 // This implements a component that renders an interactive inspector
@@ -5068,17 +5187,23 @@ class ObjectInspector extends Component 
     let objectValue;
     let label = item.name;
     let itemValue = getValue(item);
 
     const isPrimitive = nodeIsPrimitive(item);
 
     const unavailable = isPrimitive && itemValue && itemValue.hasOwnProperty && itemValue.hasOwnProperty("unavailable");
 
-    if (nodeIsOptimizedOut(item)) {
+    if (nodeIsUninitializedBinding(item)) {
+      objectValue = dom.span({ className: "unavailable" }, "(uninitialized)");
+    } else if (nodeIsUnmappedBinding(item)) {
+      objectValue = dom.span({ className: "unavailable" }, "(unmapped)");
+    } else if (nodeIsUnscopedBinding(item)) {
+      objectValue = dom.span({ className: "unavailable" }, "(unscoped)");
+    } else if (nodeIsOptimizedOut(item)) {
       objectValue = dom.span({ className: "unavailable" }, "(optimized away)");
     } else if (nodeIsMissingArguments(item) || unavailable) {
       objectValue = dom.span({ className: "unavailable" }, "(unavailable)");
     } else if (nodeIsFunction(item) && !nodeIsGetter(item) && !nodeIsSetter(item) && (this.props.mode === MODE.TINY || !this.props.mode)) {
       objectValue = undefined;
       label = this.renderGrip(item, Object.assign({}, this.props, {
         functionName: label
       }));
@@ -5242,17 +5367,17 @@ module.exports = {
 
 "use strict";
 
 
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
 
-var _react = __webpack_require__(7);
+var _react = __webpack_require__(8);
 
 var _react2 = _interopRequireDefault(_react);
 
 var _reactDomFactories = __webpack_require__(1);
 
 var _reactDomFactories2 = _interopRequireDefault(_reactDomFactories);
 
 var _propTypes = __webpack_require__(2);
@@ -6115,33 +6240,33 @@ var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBP
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const {
   enumEntries,
   enumIndexedProperties,
   enumNonIndexedProperties,
   getPrototype,
   enumSymbols
-} = __webpack_require__(17);
+} = __webpack_require__(18);
 
 const {
   getClosestGripNode,
   getClosestNonBucketNode,
   getValue,
   nodeHasAccessors,
   nodeHasAllEntriesInPreview,
   nodeHasProperties,
   nodeIsBucket,
   nodeIsDefaultProperties,
   nodeIsEntries,
   nodeIsMapEntry,
   nodeIsPrimitive,
   nodeIsProxy,
   nodeNeedsNumericalBuckets
-} = __webpack_require__(18);
+} = __webpack_require__(19);
 
 function loadItemProperties(item, createObjectClient, loadedProperties) {
   const [start, end] = item.meta ? [item.meta.startIndex, item.meta.endIndex] : [];
 
   let objectClient;
   const getObjectClient = () => {
     if (objectClient) {
       return objectClient;