Bug 1380709 - Fetch Map/Set entries when expanding the Object Inspector. r= draft
authorNicolas Chevobbe <nchevobbe@mozilla.com>
Thu, 20 Jul 2017 16:10:20 +0100
changeset 618440 8e92fdc8640279fa44f44a0470aa3918c59b74a3
parent 618288 a6c502679d442596b8b4410d6413cd310ac46408
child 640065 bd85b8b5309e5c19c009a4875b047199249c313e
push id71328
push userbmo:nchevobbe@mozilla.com
push dateMon, 31 Jul 2017 13:22:09 +0000
bugs1380709
milestone56.0a1
Bug 1380709 - Fetch Map/Set entries when expanding the Object Inspector. r= MozReview-Commit-ID: 3RSCpxTxeMF
devtools/client/shared/components/reps/reps.css
devtools/client/shared/components/reps/reps.js
devtools/client/webconsole/new-console-output/actions/messages.js
devtools/client/webconsole/new-console-output/components/console-output.js
devtools/client/webconsole/new-console-output/components/grip-message-body.js
devtools/client/webconsole/new-console-output/components/message-container.js
devtools/client/webconsole/new-console-output/components/message-types/console-api-call.js
devtools/client/webconsole/new-console-output/components/message-types/evaluation-result.js
devtools/client/webconsole/new-console-output/constants.js
devtools/client/webconsole/new-console-output/reducers/messages.js
devtools/client/webconsole/new-console-output/selectors/messages.js
devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/stub-snippets.js
devtools/client/webconsole/new-console-output/test/fixtures/stubs/consoleApi.js
devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_object_inspector_entries.js
devtools/client/webconsole/new-console-output/test/store/messages.test.js
devtools/client/webconsole/new-console-output/test/store/release-actors.test.js
devtools/client/webconsole/new-console-output/test/store/search.test.js
--- a/devtools/client/shared/components/reps/reps.css
+++ b/devtools/client/shared/components/reps/reps.css
@@ -249,8 +249,12 @@ html[dir="rtl"] .arrow svg,
 
 .arrow.expanded.expanded svg {
   transform: rotate(0deg);
 }
 
 .object-label {
   color: var(--theme-highlight-blue);
 }
+
+.default-property {
+  opacity: 0.6;
+}
--- a/devtools/client/shared/components/reps/reps.js
+++ b/devtools/client/shared/components/reps/reps.js
@@ -55,17 +55,17 @@ return /******/ (function(modules) { // 
 /***/ function(module, exports, __webpack_require__) {
 
 	/* 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__(1);
 	const { REPS, getRep } = __webpack_require__(2);
-	const ObjectInspector = __webpack_require__(44);
+	const ObjectInspector = __webpack_require__(45);
 
 	const {
 	  parseURLEncodedText,
 	  parseURLParams,
 	  maybeEscapePropertyName,
 	  getGripPreviewItems
 	} = __webpack_require__(7);
 
@@ -133,22 +133,23 @@ return /******/ (function(modules) { // 
 	const ElementNode = __webpack_require__(32);
 	const TextNode = __webpack_require__(37);
 	const ErrorRep = __webpack_require__(38);
 	const Window = __webpack_require__(39);
 	const ObjectWithText = __webpack_require__(40);
 	const ObjectWithURL = __webpack_require__(41);
 	const GripArray = __webpack_require__(42);
 	const GripMap = __webpack_require__(43);
+	const GripMapEntry = __webpack_require__(44);
 	const Grip = __webpack_require__(17);
 
 	// 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, Grip, Undefined, Null, StringRep, Number, SymbolRep, InfinityRep, NaNRep, Accessor];
+	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];
 
 	/**
 	 * 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) {
@@ -215,16 +216,17 @@ return /******/ (function(modules) { // 
 	    Document,
 	    ElementNode,
 	    ErrorRep,
 	    Event,
 	    Func,
 	    Grip,
 	    GripArray,
 	    GripMap,
+	    GripMapEntry,
 	    InfinityRep,
 	    LongStringRep,
 	    NaNRep,
 	    Null,
 	    Number,
 	    Obj,
 	    ObjectWithText,
 	    ObjectWithURL,
@@ -1272,16 +1274,17 @@ return /******/ (function(modules) { // 
 	  // 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({}, props, {
+	      className: "nodeName",
 	      object: name,
 	      mode: mode || MODE.TINY,
 	      defaultRep: Grip
 	    }));
 	  }
 
 	  return [key, span({
 	    "className": "objectEqual"
@@ -1337,21 +1340,17 @@ return /******/ (function(modules) { // 
 	  } = props;
 
 	  const config = {
 	    "data-link-actor-id": object.actor,
 	    className: "objectBox objectBox-object"
 	  };
 
 	  if (mode === MODE.TINY) {
-	    let propertiesLength = object.preview && object.preview.ownPropertiesLength ? object.preview.ownPropertiesLength : object.ownPropertyLength;
-
-	    if (object.preview && object.preview.safeGetterValues) {
-	      propertiesLength += Object.keys(object.preview.safeGetterValues).length;
-	    }
+	    let propertiesLength = getPropertiesLength(object);
 
 	    const tinyModeItems = [];
 	    if (getTitle(props, object) !== DEFAULT_TITLE) {
 	      tinyModeItems.push(getTitleElement(props, object));
 	    } else {
 	      tinyModeItems.push(span({
 	        className: "objectLeftBrace"
 	      }, "{"), propertiesLength > 0 ? span({
@@ -1380,16 +1379,30 @@ return /******/ (function(modules) { // 
 	    className: "objectTitle"
 	  }, getTitle(props, object));
 	}
 
 	function getTitle(props, object) {
 	  return props.title || object.class || DEFAULT_TITLE;
 	}
 
+	function getPropertiesLength(object) {
+	  let propertiesLength = object.preview && object.preview.ownPropertiesLength ? object.preview.ownPropertiesLength : object.ownPropertyLength;
+
+	  if (object.preview && object.preview.safeGetterValues) {
+	    propertiesLength += Object.keys(object.preview.safeGetterValues).length;
+	  }
+
+	  if (object.preview && object.preview.ownSymbols) {
+	    propertiesLength += object.preview.ownSymbolsLength;
+	  }
+
+	  return propertiesLength;
+	}
+
 	function safePropIterator(props, object, max) {
 	  max = typeof max === "undefined" ? maxLengthMap.get(MODE.SHORT) : max;
 	  try {
 	    return propIterator(props, object, max);
 	  } catch (err) {
 	    console.error(err);
 	  }
 	  return [];
@@ -1407,37 +1420,57 @@ return /******/ (function(modules) { // 
 	  }
 
 	  // 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;
+	  let propertiesLength = getPropertiesLength(object);
 
 	  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);
 	    }));
 	  }
 
 	  // 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, suppressQuotes);
+
+	  // Show symbols.
+	  if (object.preview && object.preview.ownSymbols) {
+	    const { ownSymbols } = object.preview;
+	    const length = max - indexes.length;
+
+	    const symbolsProps = ownSymbols.slice(0, length).map(symbolItem => {
+	      return PropRep(Object.assign({}, props, {
+	        mode: MODE.TINY,
+	        name: symbolItem,
+	        object: symbolItem.descriptor.value,
+	        equal: ": ",
+	        defaultRep: Grip,
+	        title: null,
+	        suppressQuotes
+	      }));
+	    });
+
+	    propsArray.push(...symbolsProps);
+	  }
+
 	  if (Object.keys(properties).length > max || propertiesLength > max
 	  // When the object has non-enumerable properties, we don't have them in the packet,
 	  // but we might want to show there's something in the object.
 	  || propertiesLength > propsArray.length) {
 	    // There are some undisplayed props. Then display "more...".
 	    propsArray.push(span({
 	      key: "more",
 	      className: "more-ellipsis",
@@ -1551,17 +1584,18 @@ return /******/ (function(modules) { // 
 	  return value;
 	}
 
 	// Registration
 	function supportsObject(object, type, noGrip = false) {
 	  if (noGrip === true || !isGrip(object)) {
 	    return false;
 	  }
-	  return object.preview && object.preview.ownProperties;
+
+	  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.
 	let Grip = {
@@ -1592,20 +1626,23 @@ return /******/ (function(modules) { // 
 	/**
 	 * Renders a symbol.
 	 */
 	SymbolRep.propTypes = {
 	  object: React.PropTypes.object.isRequired
 	};
 
 	function SymbolRep(props) {
-	  let { object } = props;
+	  let {
+	    className = "objectBox objectBox-symbol",
+	    object
+	  } = props;
 	  let { name } = object;
 
-	  return span({ className: "objectBox objectBox-symbol" }, `Symbol(${name || ""})`);
+	  return span({ className }, `Symbol(${name || ""})`);
 	}
 
 	function supportsObject(object, type) {
 	  return type == "symbol";
 	}
 
 	// Exports from this module
 	module.exports = {
@@ -3488,17 +3525,17 @@ return /******/ (function(modules) { // 
 	  });
 
 	  return indexes.map((index, i) => {
 	    let [key, entryValue] = entries[index];
 	    let value = entryValue.value !== undefined ? entryValue.value : entryValue;
 
 	    return PropRep({
 	      name: key,
-	      equal: ": ",
+	      equal: " \u2192 ",
 	      object: value,
 	      mode: MODE.TINY,
 	      onDOMNodeMouseOver,
 	      onDOMNodeMouseOut,
 	      onInspectIconClick
 	    });
 	  });
 	}
@@ -3549,45 +3586,126 @@ return /******/ (function(modules) { // 
 /***/ },
 /* 44 */
 /***/ function(module, exports, __webpack_require__) {
 
 	/* 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 React = __webpack_require__(8);
+	// Shortcuts
+	const { span } = React.DOM;
+	const {
+	  wrapRender
+	} = __webpack_require__(7);
+	const PropRep = __webpack_require__(16);
+	const { MODE } = __webpack_require__(1);
+	/**
+	 * Renders an map entry. A map entry is represented by its key, a column and its value.
+	 */
+	GripMapEntry.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])),
+	  onDOMNodeMouseOver: React.PropTypes.func,
+	  onDOMNodeMouseOut: React.PropTypes.func,
+	  onInspectIconClick: React.PropTypes.func
+	};
+
+	function GripMapEntry(props) {
+	  const {
+	    object
+	  } = props;
+
+	  const {
+	    key,
+	    value
+	  } = object.preview;
+
+	  return span({
+	    className: "objectBox objectBox-map-entry"
+	  }, ...PropRep(Object.assign({}, props, {
+	    name: key,
+	    object: value,
+	    equal: " \u2192 ",
+	    title: null,
+	    suppressQuotes: false
+	  })));
+	}
+
+	function supportsObject(grip, type, noGrip = false) {
+	  if (noGrip === true) {
+	    return false;
+	  }
+	  return grip && grip.type === "mapEntry" && grip.preview;
+	}
+
+	function createGripMapEntry(key, value) {
+	  return {
+	    type: "mapEntry",
+	    preview: {
+	      key,
+	      value
+	    }
+	  };
+	}
+
+	// Exports from this module
+	module.exports = {
+	  rep: wrapRender(GripMapEntry),
+	  createGripMapEntry,
+	  supportsObject
+	};
+
+/***/ },
+/* 45 */
+/***/ function(module, exports, __webpack_require__) {
+
+	/* 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,
 	  DOM: dom,
 	  PropTypes
 	} = __webpack_require__(8);
 
-	const Tree = createFactory(__webpack_require__(45).Tree);
-	__webpack_require__(47);
-
-	const classnames = __webpack_require__(49);
+	const Tree = createFactory(__webpack_require__(46).Tree);
+	__webpack_require__(48);
+
+	const classnames = __webpack_require__(50);
 	const Svg = __webpack_require__(33);
 	const {
-	  REPS: { Rep }
+	  REPS: {
+	    Rep,
+	    Grip
+	  }
 	} = __webpack_require__(2);
 	const {
 	  MODE
 	} = __webpack_require__(1);
 
 	const {
 	  getChildren,
+	  getParent,
 	  getValue,
-	  isDefault,
+	  nodeHasAllEntriesInPreview,
+	  nodeIsDefault,
 	  nodeHasAccessors,
 	  nodeHasProperties,
+	  nodeIsEntries,
+	  nodeIsMapEntry,
 	  nodeIsMissingArguments,
 	  nodeIsOptimizedOut,
 	  nodeIsPrimitive
-	} = __webpack_require__(50);
+	} = __webpack_require__(51);
 
 	// This implements a component that renders an interactive inspector
 	// for looking at JavaScript objects. It expects descriptions of
 	// objects from the protocol, and will dynamically fetch child
 	// properties as objects are expanded.
 	//
 	// If you want to inspect a single object, pass the name and the
 	// protocol descriptor of it:
@@ -3627,28 +3745,28 @@ return /******/ (function(modules) { // 
 	    self.renderTreeItem = this.renderTreeItem.bind(this);
 	    self.setExpanded = this.setExpanded.bind(this);
 	    self.focusItem = this.focusItem.bind(this);
 	    self.getRoots = this.getRoots.bind(this);
 	  }
 
 	  isDefaultProperty(item) {
 	    const roots = this.props.roots;
-	    return isDefault(item, roots);
-	  }
-
-	  getParent(item) {
-	    return null;
+	    return nodeIsDefault(item, roots);
 	  }
 
 	  getChildren(item) {
-	    const { getObjectProperties } = this.props;
+	    const {
+	      getObjectEntries,
+	      getObjectProperties
+	    } = this.props;
 	    const { actors } = this;
 
 	    return getChildren({
+	      getObjectEntries,
 	      getObjectProperties,
 	      actors,
 	      item
 	    });
 	  }
 
 	  getRoots() {
 	    return this.props.roots;
@@ -3668,22 +3786,33 @@ return /******/ (function(modules) { // 
 	      expandedKeys.delete(key);
 	    }
 
 	    this.setState({ expandedKeys });
 
 	    if (expand === true) {
 	      const {
 	        getObjectProperties,
-	        loadObjectProperties
+	        getObjectEntries,
+	        loadObjectProperties,
+	        loadObjectEntries
 	      } = this.props;
 
 	      const value = getValue(item);
+	      const parent = getParent(item);
+	      const parentValue = getValue(parent);
+	      const parentActor = parentValue ? parentValue.actor : null;
+
 	      if (nodeHasProperties(item) && value && !getObjectProperties(value.actor)) {
 	        loadObjectProperties(value);
+	        return;
+	      }
+
+	      if (nodeIsEntries(item) && !nodeHasAllEntriesInPreview(parent) && !getObjectEntries(parentActor)) {
+	        loadObjectEntries(parentValue);
 	      }
 	    }
 	  }
 
 	  focusItem(item) {
 	    if (!this.props.disabledFocus && this.state.focusedItem !== item) {
 	      this.setState({
 	        focusedItem: item
@@ -3703,25 +3832,28 @@ return /******/ (function(modules) { // 
 	    const isPrimitive = nodeIsPrimitive(item);
 
 	    const unavailable = isPrimitive && itemValue && itemValue.hasOwnProperty && itemValue.hasOwnProperty("unavailable");
 
 	    if (nodeIsOptimizedOut(item)) {
 	      objectValue = dom.span({ className: "unavailable" }, "(optimized away)");
 	    } else if (nodeIsMissingArguments(item) || unavailable) {
 	      objectValue = dom.span({ className: "unavailable" }, "(unavailable)");
-	    } else if (nodeHasProperties(item) || nodeHasAccessors(item) || isPrimitive) {
+	    } else if (nodeHasProperties(item) || nodeHasAccessors(item) || nodeIsMapEntry(item) || isPrimitive) {
 	      let repsProp = Object.assign({}, this.props);
 	      if (depth > 0) {
 	        repsProp.mode = this.props.mode === MODE.LONG ? MODE.SHORT : MODE.TINY;
 	      }
 
 	      objectValue = this.renderGrip(item, repsProp);
 	    }
 
+	    const hasLabel = label !== null && typeof label !== "undefined";
+	    const hasValue = typeof objectValue !== "undefined";
+
 	    const SINGLE_INDENT_WIDTH = 15;
 	    const indentWidth = (depth + (isPrimitive ? 1 : 0)) * SINGLE_INDENT_WIDTH;
 
 	    const {
 	      onDoubleClick,
 	      onLabelClick
 	    } = this.props;
 
@@ -3744,35 +3876,36 @@ return /******/ (function(modules) { // 
 	          focused,
 	          expanded
 	        });
 	      } : null
 	    }, isPrimitive === false ? Svg("arrow", {
 	      className: classnames({
 	        expanded: expanded
 	      })
-	    }) : null, label ? dom.span({
+	    }) : null, hasLabel ? dom.span({
 	      className: "object-label",
 	      onClick: onLabelClick ? event => {
 	        event.stopPropagation();
 	        onLabelClick(item, {
 	          depth,
 	          focused,
 	          expanded,
 	          setExpanded: this.setExpanded
 	        });
 	      } : null
-	    }, label) : null, label && objectValue ? dom.span({ className: "object-delimiter" }, " : ") : null, objectValue);
+	    }, label) : null, hasLabel && hasValue ? dom.span({ className: "object-delimiter" }, " : ") : null, hasValue ? objectValue : null);
 	  }
 
 	  renderGrip(item, props) {
 	    const object = getValue(item);
 	    return Rep(Object.assign({}, props, {
 	      object,
-	      mode: props.mode || MODE.TINY
+	      mode: props.mode || MODE.TINY,
+	      defaultRep: Grip
 	    }));
 	  }
 
 	  render() {
 	    const {
 	      autoExpandDepth = 1,
 	      autoExpandAll = true,
 	      disabledFocus,
@@ -3796,17 +3929,17 @@ return /******/ (function(modules) { // 
 	      autoExpandDepth,
 	      disabledFocus,
 	      itemHeight,
 
 	      isExpanded: item => expandedKeys.has(this.getKey(item)),
 	      focused: focusedItem,
 
 	      getRoots: this.getRoots,
-	      getParent: this.getParent,
+	      getParent,
 	      getChildren: this.getChildren,
 	      getKey: this.getKey,
 
 	      onExpand: item => this.setExpanded(item, true),
 	      onCollapse: item => this.setExpanded(item, false),
 	      onFocus: this.focusItem,
 
 	      renderItem: this.renderTreeItem
@@ -3829,27 +3962,27 @@ return /******/ (function(modules) { // 
 	  onFocus: PropTypes.func,
 	  onDoubleClick: PropTypes.func,
 	  onLabelClick: PropTypes.func
 	};
 
 	module.exports = ObjectInspector;
 
 /***/ },
-/* 45 */
+/* 46 */
 /***/ function(module, exports, __webpack_require__) {
 
-	const Tree = __webpack_require__(46);
+	const Tree = __webpack_require__(47);
 
 	module.exports = {
 	  Tree
 	};
 
 /***/ },
-/* 46 */
+/* 47 */
 /***/ function(module, exports, __webpack_require__) {
 
 	const { DOM: dom, createClass, createFactory, PropTypes } = __webpack_require__(8);
 
 	const AUTO_EXPAND_DEPTH = 0; // depth
 
 	/**
 	 * An arrow that displays whether its node is expanded (▼) or collapsed
@@ -4383,24 +4516,24 @@ return /******/ (function(modules) { // 
 	      }
 	    }
 
 	    this._focus(parentIndex, parent);
 	  })
 	});
 
 /***/ },
-/* 47 */
+/* 48 */
 /***/ function(module, exports) {
 
 	// removed by extract-text-webpack-plugin
 
 /***/ },
-/* 48 */,
-/* 49 */
+/* 49 */,
+/* 50 */
 /***/ function(module, exports, __webpack_require__) {
 
 	var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*!
 	  Copyright (c) 2016 Jed Watson.
 	  Licensed under the MIT License (MIT), see
 	  http://jedwatson.github.io/classnames
 	*/
 	/* global define */
@@ -4444,63 +4577,93 @@ return /******/ (function(modules) { // 
 			}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
 		} else {
 			window.classNames = classNames;
 		}
 	}());
 
 
 /***/ },
-/* 50 */
+/* 51 */
 /***/ function(module, exports, __webpack_require__) {
 
 	/* 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 = __webpack_require__(51);
-	const has = __webpack_require__(103);
+	const get = __webpack_require__(52);
+	const has = __webpack_require__(104);
 	const { maybeEscapePropertyName } = __webpack_require__(7);
+	const GripMapEntry = __webpack_require__(44);
+
+	const NODE_TYPES = {
+	  BUCKET: Symbol("[n…n]"),
+	  DEFAULT_PROPERTIES: Symbol("[default properties]"),
+	  ENTRIES: Symbol("<entries>"),
+	  GET: Symbol("<get>"),
+	  GRIP: Symbol("GRIP"),
+	  MAP_ENTRY_KEY: Symbol("<key>"),
+	  MAP_ENTRY_VALUE: Symbol("<value>"),
+	  PROMISE_REASON: Symbol("<reason>"),
+	  PROMISE_STATE: Symbol("<state>"),
+	  PROMISE_VALUE: Symbol("<value>"),
+	  SET: Symbol("<set>")
+	};
 
 	let WINDOW_PROPERTIES = {};
 
 	if (typeof window === "object") {
 	  WINDOW_PROPERTIES = Object.getOwnPropertyNames(window);
 	}
 
+	const SAFE_PATH_PREFIX = "##-";
+
+	function getType(item) {
+	  return item.type;
+	}
+
 	function getValue(item) {
 	  if (has(item, "contents.value")) {
 	    return get(item, "contents.value");
 	  }
 
 	  if (has(item, "contents.getterValue")) {
 	    return get(item, "contents.getterValue", undefined);
 	  }
 
 	  if (nodeHasAccessors(item)) {
 	    return item.contents;
 	  }
 
 	  return undefined;
 	}
 
-	function isBucket(item) {
-	  return item.path && item.path.match(/bucket(\d+)$/);
+	function nodeIsBucket(item) {
+	  return getType(item) === NODE_TYPES.BUCKET;
+	}
+
+	function nodeIsEntries(item) {
+	  return getType(item) === NODE_TYPES.ENTRIES;
+	}
+
+	function nodeIsMapEntry(item) {
+	  return GripMapEntry.supportsObject(getValue(item));
 	}
 
 	function nodeHasChildren(item) {
-	  return Array.isArray(item.contents) || isBucket(item);
+	  return Array.isArray(item.contents) || nodeIsBucket(item);
 	}
 
 	function nodeIsObject(item) {
 	  const value = getValue(item);
 	  return value && value.type === "object";
 	}
 
-	function nodeIsArray(value) {
+	function nodeIsArray(item) {
+	  const value = getValue(item);
 	  return value && value.class === "Array";
 	}
 
 	function nodeIsFunction(item) {
 	  const value = getValue(item);
 	  return value && value.class === "Function";
 	}
 
@@ -4514,257 +4677,385 @@ return /******/ (function(modules) { // 
 	  return !nodeHasChildren(item) && value && value.missingArguments;
 	}
 
 	function nodeHasProperties(item) {
 	  return !nodeHasChildren(item) && nodeIsObject(item);
 	}
 
 	function nodeIsPrimitive(item) {
-	  return !nodeHasChildren(item) && !nodeHasProperties(item) && !nodeHasAccessors(item);
-	}
-
-	function isPromise(item) {
+	  return !nodeHasChildren(item) && !nodeHasProperties(item) && !nodeIsEntries(item) && !nodeIsMapEntry(item) && !nodeHasAccessors(item);
+	}
+
+	function nodeIsDefault(item, roots) {
+	  if (roots && roots.length === 1) {
+	    const value = getValue(roots[0]);
+	    return value.class === "Window";
+	  }
+	  return WINDOW_PROPERTIES.includes(item.name);
+	}
+
+	function isDefaultWindowProperty(name) {
+	  return WINDOW_PROPERTIES.includes(name);
+	}
+
+	function nodeIsPromise(item) {
 	  const value = getValue(item);
+	  if (!value) {
+	    return false;
+	  }
+
 	  return value.class == "Promise";
 	}
 
-	function getPromiseProperties(item) {
+	function nodeHasAccessors(item) {
+	  return !!getNodeGetter(item) || !!getNodeSetter(item);
+	}
+
+	function nodeHasEntries(item) {
+	  const value = getValue(item);
+	  if (!value) {
+	    return false;
+	  }
+
+	  return value.class === "Map" || value.class === "Set" || value.class === "WeakMap" || value.class === "WeakSet";
+	}
+
+	function nodeHasAllEntriesInPreview(item) {
+	  const { preview } = getValue(item) || {};
+	  if (!preview) {
+	    return false;
+	  }
+
+	  const {
+	    entries,
+	    items,
+	    length,
+	    size
+	  } = preview;
+
+	  return entries ? entries.length === size : items.length === length;
+	}
+
+	function nodeSupportsBucketing(item) {
+	  return nodeIsArray(item) || nodeIsEntries(item);
+	}
+
+	function makeNodesForPromiseProperties(item) {
 	  const { promiseState: { reason, value, state } } = getValue(item);
 
 	  const properties = [];
 
 	  if (state) {
-	    properties.push(createNode("<state>", `${item.path}/state`, { value: state }));
+	    properties.push(createNode(item, "<state>", `${item.path}/${SAFE_PATH_PREFIX}state`, { value: state }, NODE_TYPES.PROMISE_STATE));
 	  }
 
 	  if (reason) {
-	    properties.push(createNode("<reason>", `${item.path}/reason`, { value: reason }));
+	    properties.push(createNode(item, "<reason>", `${item.path}/${SAFE_PATH_PREFIX}reason`, { value: reason }, NODE_TYPES.PROMISE_REASON));
 	  }
 
 	  if (value) {
-	    properties.push(createNode("<value>", `${item.path}/value`, { value: value }));
+	    properties.push(createNode(item, "<value>", `${item.path}/${SAFE_PATH_PREFIX}value`, { value: value }, NODE_TYPES.PROMISE_VALUE));
 	  }
 
 	  return properties;
 	}
 
+	function makeNodesForEntries(item) {
+	  const { path } = item;
+	  const { preview } = getValue(item);
+	  const nodeName = "<entries>";
+	  const entriesPath = `${path}/${SAFE_PATH_PREFIX}entries`;
+
+	  if (nodeHasAllEntriesInPreview(item)) {
+	    let entriesNodes = [];
+	    if (preview.entries) {
+	      entriesNodes = preview.entries.map(([key, value], index) => {
+	        return createNode(item, index, `${entriesPath}/${index}`, { value: GripMapEntry.createGripMapEntry(key, value) });
+	      });
+	    } else if (preview.items) {
+	      entriesNodes = preview.items.map((value, index) => {
+	        return createNode(item, index, `${entriesPath}/${index}`, { value });
+	      });
+	    }
+	    return createNode(item, nodeName, entriesPath, entriesNodes, NODE_TYPES.ENTRIES);
+	  }
+	  return createNode(item, nodeName, entriesPath, null, NODE_TYPES.ENTRIES);
+	}
+
+	function makeNodesForMapEntry(item) {
+	  const nodeValue = getValue(item);
+	  if (!nodeValue || !nodeValue.preview) {
+	    return [];
+	  }
+
+	  const { key, value } = nodeValue.preview;
+	  const path = item.path;
+
+	  return [createNode(item, "<key>", `${path}/${SAFE_PATH_PREFIX}key`, { value: key }, NODE_TYPES.MAP_ENTRY_KEY), createNode(item, "<value>", `${path}/${SAFE_PATH_PREFIX}value`, { value }, NODE_TYPES.MAP_ENTRY_VALUE)];
+	}
+
 	function getNodeGetter(item) {
 	  return get(item, "contents.get", undefined);
 	}
 
 	function getNodeSetter(item) {
 	  return get(item, "contents.set", undefined);
 	}
 
-	function nodeHasAccessors(item) {
-	  return !!getNodeGetter(item) || !!getNodeSetter(item);
-	}
-
-	function getAccessors(item) {
+	function makeNodesForAccessors(item) {
 	  const accessors = [];
 
 	  const getter = getNodeGetter(item);
 	  if (getter && getter.type !== "undefined") {
-	    accessors.push(createNode("<get>", `${item.path}/get`, { value: getter }));
+	    accessors.push(createNode(item, "<get>", `${item.path}/${SAFE_PATH_PREFIX}get`, { value: getter }, NODE_TYPES.GET));
 	  }
 
 	  const setter = getNodeSetter(item);
 	  if (setter && setter.type !== "undefined") {
-	    accessors.push(createNode("<set>", `${item.path}/set`, { value: setter }));
+	    accessors.push(createNode(item, "<set>", `${item.path}/${SAFE_PATH_PREFIX}set`, { value: setter }, NODE_TYPES.SET));
 	  }
 
 	  return accessors;
 	}
 
-	function isDefault(item, roots) {
-	  if (roots && roots.length === 1) {
-	    const value = getValue(roots[0]);
-	    return value.class === "Window";
-	  }
-	  return WINDOW_PROPERTIES.includes(item.name);
-	}
-
 	function sortProperties(properties) {
 	  return properties.sort((a, b) => {
 	    // Sort numbers in ascending order and sort strings lexicographically
 	    const aInt = parseInt(a, 10);
 	    const bInt = parseInt(b, 10);
 
 	    if (isNaN(aInt) || isNaN(bInt)) {
 	      return a > b ? 1 : -1;
 	    }
 
 	    return aInt - bInt;
 	  });
 	}
 
-	function makeNumericalBuckets(props, bucketSize, parentPath, ownProperties) {
-	  const numProperties = props.length;
+	function makeNumericalBuckets(propertiesNames, bucketSize, parent, ownProperties) {
+	  const parentPath = parent.path;
+	  const numProperties = propertiesNames.length;
 	  const numBuckets = Math.ceil(numProperties / bucketSize);
 	  let buckets = [];
 	  for (let i = 1; i <= numBuckets; i++) {
-	    const bucketKey = `bucket${i}`;
+	    const bucketKey = `${SAFE_PATH_PREFIX}bucket${i}`;
 	    const minKey = (i - 1) * bucketSize;
 	    const maxKey = Math.min(i * bucketSize - 1, numProperties);
 	    const bucketName = `[${minKey}..${maxKey}]`;
-	    const bucketProperties = props.slice(minKey, maxKey);
-
-	    const bucketNodes = bucketProperties.map(name => createNode(name, `${parentPath}/${bucketKey}/${name}`, ownProperties[name]));
-
-	    buckets.push(createNode(bucketName, `${parentPath}/${bucketKey}`, bucketNodes));
+	    const bucketProperties = propertiesNames.slice(minKey, maxKey);
+
+	    const bucketNodes = bucketProperties.map(name => createNode(parent, name, `${parentPath}/${bucketKey}/${name}`, ownProperties[name]));
+
+	    buckets.push(createNode(parent, bucketName, `${parentPath}/${bucketKey}`, bucketNodes, NODE_TYPES.BUCKET));
 	  }
 	  return buckets;
 	}
 
-	function makeDefaultPropsBucket(props, parentPath, ownProperties) {
-	  const userProps = props.filter(name => !isDefault({ name }));
-	  const defaultProps = props.filter(name => isDefault({ name }));
-
-	  let nodes = makeNodesForOwnProps(userProps, parentPath, ownProperties);
-
-	  if (defaultProps.length > 0) {
-	    const defaultNodes = defaultProps.map((name, index) => createNode(maybeEscapePropertyName(name), `${parentPath}/bucket${index}/${name}`, ownProperties[name]));
-	    nodes.push(createNode("[default properties]", `${parentPath}/##-default`, defaultNodes));
+	function makeDefaultPropsBucket(propertiesNames, parent, ownProperties) {
+	  const parentPath = parent.path;
+
+	  const [userPropertiesNames, defaultProperties] = propertiesNames.reduce((res, name) => {
+	    const [userProps, defaultProps] = res;
+	    (isDefaultWindowProperty(name) ? defaultProps : userProps).push(name);
+	    return res;
+	  }, [[], []]);
+
+	  let nodes = makeNodesForOwnProps(userPropertiesNames, parent, ownProperties);
+
+	  if (defaultProperties.length > 0) {
+	    const defaultPropertiesNode = createNode(parent, "[default properties]", `${parentPath}/${SAFE_PATH_PREFIX}default`, null, NODE_TYPES.DEFAULT_PROPERTIES);
+
+	    const defaultNodes = defaultProperties.map((name, index) => createNode(defaultPropertiesNode, maybeEscapePropertyName(name), `${parentPath}/${SAFE_PATH_PREFIX}bucket${index}/${name}`, ownProperties[name]));
+	    nodes.push(setNodeChildren(defaultPropertiesNode, defaultNodes));
 	  }
 	  return nodes;
 	}
 
-	function makeNodesForOwnProps(properties, parentPath, ownProperties) {
-	  return properties.map(name => createNode(maybeEscapePropertyName(name), `${parentPath}/${name}`, ownProperties[name]));
-	}
-
-	/*
-	 * Ignore properties that are neither non-concrete nor getters/setters.
-	*/
-	function makeNodesForProperties(objProps, parent, { bucketSize = 100 } = {}) {
+	function makeNodesForOwnProps(properties, parent, ownProperties) {
+	  const parentPath = parent.path;
+	  return properties.map(name => createNode(parent, maybeEscapePropertyName(name), `${parentPath}/${name}`, ownProperties[name]));
+	}
+
+	function makeNodesForProperties(objProps, parent, {
+	  bucketSize = 100
+	} = {}) {
 	  const {
-	    ownProperties,
+	    ownProperties = {},
 	    ownSymbols,
 	    prototype,
 	    safeGetterValues
 	  } = objProps;
 
 	  const parentPath = parent.path;
 	  const parentValue = getValue(parent);
 
 	  let allProperties = Object.assign({}, ownProperties, safeGetterValues);
-	  const properties = sortProperties(Object.keys(allProperties)).filter(name => allProperties[name].hasOwnProperty("value") || allProperties[name].hasOwnProperty("getterValue") || allProperties[name].hasOwnProperty("get") || allProperties[name].hasOwnProperty("set"));
-
-	  const numProperties = properties.length;
+
+	  // Ignore properties that are neither non-concrete nor getters/setters.
+	  const propertiesNames = sortProperties(Object.keys(allProperties)).filter(name => allProperties[name].hasOwnProperty("value") || allProperties[name].hasOwnProperty("getterValue") || allProperties[name].hasOwnProperty("get") || allProperties[name].hasOwnProperty("set"));
+
+	  const numProperties = propertiesNames.length;
 
 	  let nodes = [];
-	  if (nodeIsArray(prototype) && numProperties > bucketSize) {
-	    nodes = makeNumericalBuckets(properties, bucketSize, parentPath, allProperties);
-	  } else if (parentValue.class == "Window") {
-	    nodes = makeDefaultPropsBucket(properties, parentPath, allProperties);
+	  if (nodeSupportsBucketing(parent) && numProperties > bucketSize) {
+	    nodes = makeNumericalBuckets(propertiesNames, bucketSize, parent, allProperties);
+	  } else if (parentValue && parentValue.class == "Window") {
+	    nodes = makeDefaultPropsBucket(propertiesNames, parent, allProperties);
 	  } else {
-	    nodes = makeNodesForOwnProps(properties, parentPath, allProperties);
-	  }
-
-	  for (let index in ownSymbols) {
-	    nodes.push(createNode(ownSymbols[index].name, `${parentPath}/##symbol-${index}`, ownSymbols[index].descriptor));
-	  }
-
-	  if (isPromise(parent)) {
-	    nodes.push(...getPromiseProperties(parent));
+	    nodes = makeNodesForOwnProps(propertiesNames, parent, allProperties);
+	  }
+
+	  if (Array.isArray(ownSymbols)) {
+	    ownSymbols.forEach((ownSymbol, index) => {
+	      nodes.push(createNode(parent, ownSymbol.name, `${parentPath}/${SAFE_PATH_PREFIX}symbol-${index}`, ownSymbol.descriptor));
+	    }, this);
+	  }
+
+	  if (nodeIsPromise(parent)) {
+	    nodes.push(...makeNodesForPromiseProperties(parent));
+	  }
+
+	  if (nodeHasEntries(parent)) {
+	    nodes.push(makeNodesForEntries(parent));
 	  }
 
 	  // Add the prototype if it exists and is not null
 	  if (prototype && prototype.type !== "null") {
-	    nodes.push(createNode("__proto__", `${parentPath}/__proto__`, { value: prototype }));
+	    nodes.push(createNode(parent, "__proto__", `${parentPath}/__proto__`, { value: prototype }));
 	  }
 
 	  return nodes;
 	}
 
-	function createNode(name, path, contents) {
+	function createNode(parent, name, path, contents, type = NODE_TYPES.GRIP) {
 	  if (contents === undefined) {
 	    return null;
 	  }
+
 	  // The path is important to uniquely identify the item in the entire
 	  // tree. This helps debugging & optimizes React's rendering of large
 	  // lists. The path will be separated by property name,
 	  // i.e. `{ foo: { bar: { baz: 5 }}}` will have a path of `foo/bar/baz`
 	  // for the inner object.
-	  return { name, path, contents };
-	}
-
-	function getChildren({ getObjectProperties, actors, item }) {
+	  return {
+	    parent,
+	    name,
+	    path,
+	    contents,
+	    type
+	  };
+	}
+
+	function setNodeChildren(node, children) {
+	  node.contents = children;
+	  return node;
+	}
+
+	function getChildren(options) {
+	  const {
+	    actors = {},
+	    getObjectEntries,
+	    getObjectProperties,
+	    item
+	  } = options;
 	  // Nodes can either have children already, or be an object with
 	  // properties that we need to go and fetch.
 	  if (nodeHasAccessors(item)) {
-	    return getAccessors(item);
+	    return makeNodesForAccessors(item);
+	  }
+
+	  if (nodeIsMapEntry(item)) {
+	    return makeNodesForMapEntry(item);
 	  }
 
 	  if (nodeHasChildren(item)) {
 	    return item.contents;
 	  }
 
-	  if (!nodeHasProperties(item)) {
+	  if (!nodeHasProperties(item) && !nodeIsEntries(item)) {
 	    return [];
 	  }
 
 	  // Because we are dynamically creating the tree as the user
 	  // expands it (not precalculated tree structure), we cache child
 	  // arrays. This not only helps performance, but is necessary
 	  // because the expanded state depends on instances of nodes
 	  // being the same across renders. If we didn't do this, each
 	  // node would be a new instance every render.
 	  const key = item.path;
 	  if (actors && actors[key]) {
 	    return actors[key];
 	  }
 
-	  if (isBucket(item)) {
+	  if (nodeIsBucket(item)) {
 	    return item.contents.children;
 	  }
 
-	  const actor = get(getValue(item), "actor", undefined);
-	  const loadedProps = getObjectProperties(actor);
+	  let loadedProps;
+	  if (nodeIsEntries(item)) {
+	    // If `item` is an <entries> node, we need to get the entries
+	    // matching the parent node actor.
+	    const parent = getParent(item);
+	    loadedProps = getObjectEntries(get(getValue(parent), "actor", undefined));
+	  } else {
+	    loadedProps = getObjectProperties(get(getValue(item), "actor", undefined));
+	  }
+
 	  const {
 	    ownProperties,
 	    ownSymbols,
 	    safeGetterValues,
 	    prototype
 	  } = loadedProps || {};
 
 	  if (!ownProperties && !ownSymbols && !safeGetterValues && !prototype) {
 	    return [];
 	  }
 
 	  let children = makeNodesForProperties(loadedProps, item);
 	  actors[key] = children;
 	  return children;
 	}
 
+	function getParent(item) {
+	  return item.parent;
+	}
+
 	module.exports = {
 	  createNode,
 	  getChildren,
-	  getPromiseProperties,
+	  getParent,
 	  getValue,
-	  isDefault,
-	  isPromise,
+	  makeNodesForPromiseProperties,
 	  makeNodesForProperties,
 	  nodeHasAccessors,
+	  nodeHasAllEntriesInPreview,
 	  nodeHasChildren,
 	  nodeHasProperties,
+	  nodeIsDefault,
+	  nodeIsEntries,
 	  nodeIsFunction,
+	  nodeIsMapEntry,
 	  nodeIsMissingArguments,
 	  nodeIsObject,
 	  nodeIsOptimizedOut,
 	  nodeIsPrimitive,
-	  sortProperties
+	  nodeIsPromise,
+	  sortProperties,
+	  NODE_TYPES,
+	  // Export for testing purpose.
+	  SAFE_PATH_PREFIX
 	};
 
 /***/ },
-/* 51 */
+/* 52 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var baseGet = __webpack_require__(52);
+	var baseGet = __webpack_require__(53);
 
 	/**
 	 * Gets the value at `path` of `object`. If the resolved value is
 	 * `undefined`, the `defaultValue` is returned in its place.
 	 *
 	 * @static
 	 * @memberOf _
 	 * @since 3.7.0
@@ -4790,21 +5081,21 @@ return /******/ (function(modules) { // 
 	  var result = object == null ? undefined : baseGet(object, path);
 	  return result === undefined ? defaultValue : result;
 	}
 
 	module.exports = get;
 
 
 /***/ },
-/* 52 */
+/* 53 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var castPath = __webpack_require__(53),
-	    toKey = __webpack_require__(102);
+	var castPath = __webpack_require__(54),
+	    toKey = __webpack_require__(103);
 
 	/**
 	 * The base implementation of `_.get` without support for default values.
 	 *
 	 * @private
 	 * @param {Object} object The object to query.
 	 * @param {Array|string} path The path of the property to get.
 	 * @returns {*} Returns the resolved value.
@@ -4820,23 +5111,23 @@ return /******/ (function(modules) { // 
 	  }
 	  return (index && index == length) ? object : undefined;
 	}
 
 	module.exports = baseGet;
 
 
 /***/ },
-/* 53 */
+/* 54 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var isArray = __webpack_require__(54),
-	    isKey = __webpack_require__(55),
-	    stringToPath = __webpack_require__(64),
-	    toString = __webpack_require__(99);
+	var isArray = __webpack_require__(55),
+	    isKey = __webpack_require__(56),
+	    stringToPath = __webpack_require__(65),
+	    toString = __webpack_require__(100);
 
 	/**
 	 * Casts `value` to a path array if it's not one.
 	 *
 	 * @private
 	 * @param {*} value The value to inspect.
 	 * @param {Object} [object] The object to query keys on.
 	 * @returns {Array} Returns the cast property path array.
@@ -4847,17 +5138,17 @@ return /******/ (function(modules) { // 
 	  }
 	  return isKey(value, object) ? [value] : stringToPath(toString(value));
 	}
 
 	module.exports = castPath;
 
 
 /***/ },
-/* 54 */
+/* 55 */
 /***/ function(module, exports) {
 
 	/**
 	 * Checks if `value` is classified as an `Array` object.
 	 *
 	 * @static
 	 * @memberOf _
 	 * @since 0.1.0
@@ -4879,21 +5170,21 @@ return /******/ (function(modules) { // 
 	 * // => false
 	 */
 	var isArray = Array.isArray;
 
 	module.exports = isArray;
 
 
 /***/ },
-/* 55 */
+/* 56 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var isArray = __webpack_require__(54),
-	    isSymbol = __webpack_require__(56);
+	var isArray = __webpack_require__(55),
+	    isSymbol = __webpack_require__(57);
 
 	/** Used to match property names within property paths. */
 	var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,
 	    reIsPlainProp = /^\w*$/;
 
 	/**
 	 * Checks if `value` is a property name and not a property path.
 	 *
@@ -4914,21 +5205,21 @@ return /******/ (function(modules) { // 
 	  return reIsPlainProp.test(value) || !reIsDeepProp.test(value) ||
 	    (object != null && value in Object(object));
 	}
 
 	module.exports = isKey;
 
 
 /***/ },
-/* 56 */
+/* 57 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var baseGetTag = __webpack_require__(57),
-	    isObjectLike = __webpack_require__(63);
+	var baseGetTag = __webpack_require__(58),
+	    isObjectLike = __webpack_require__(64);
 
 	/** `Object#toString` result references. */
 	var symbolTag = '[object Symbol]';
 
 	/**
 	 * Checks if `value` is classified as a `Symbol` primitive or object.
 	 *
 	 * @static
@@ -4949,22 +5240,22 @@ return /******/ (function(modules) { // 
 	  return typeof value == 'symbol' ||
 	    (isObjectLike(value) && baseGetTag(value) == symbolTag);
 	}
 
 	module.exports = isSymbol;
 
 
 /***/ },
-/* 57 */
+/* 58 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var Symbol = __webpack_require__(58),
-	    getRawTag = __webpack_require__(61),
-	    objectToString = __webpack_require__(62);
+	var Symbol = __webpack_require__(59),
+	    getRawTag = __webpack_require__(62),
+	    objectToString = __webpack_require__(63);
 
 	/** `Object#toString` result references. */
 	var nullTag = '[object Null]',
 	    undefinedTag = '[object Undefined]';
 
 	/** Built-in value references. */
 	var symToStringTag = Symbol ? Symbol.toStringTag : undefined;
 
@@ -4983,58 +5274,58 @@ return /******/ (function(modules) { // 
 	    ? getRawTag(value)
 	    : objectToString(value);
 	}
 
 	module.exports = baseGetTag;
 
 
 /***/ },
-/* 58 */
+/* 59 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var root = __webpack_require__(59);
+	var root = __webpack_require__(60);
 
 	/** Built-in value references. */
 	var Symbol = root.Symbol;
 
 	module.exports = Symbol;
 
 
 /***/ },
-/* 59 */
+/* 60 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var freeGlobal = __webpack_require__(60);
+	var freeGlobal = __webpack_require__(61);
 
 	/** Detect free variable `self`. */
 	var freeSelf = typeof self == 'object' && self && self.Object === Object && self;
 
 	/** Used as a reference to the global object. */
 	var root = freeGlobal || freeSelf || Function('return this')();
 
 	module.exports = root;
 
 
 /***/ },
-/* 60 */
+/* 61 */
 /***/ function(module, exports) {
 
 	/* WEBPACK VAR INJECTION */(function(global) {/** Detect free variable `global` from Node.js. */
 	var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;
 
 	module.exports = freeGlobal;
 
 	/* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))
 
 /***/ },
-/* 61 */
+/* 62 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var Symbol = __webpack_require__(58);
+	var Symbol = __webpack_require__(59);
 
 	/** Used for built-in method references. */
 	var objectProto = Object.prototype;
 
 	/** Used to check objects for own properties. */
 	var hasOwnProperty = objectProto.hasOwnProperty;
 
 	/**
@@ -5073,17 +5364,17 @@ return /******/ (function(modules) { // 
 	  }
 	  return result;
 	}
 
 	module.exports = getRawTag;
 
 
 /***/ },
-/* 62 */
+/* 63 */
 /***/ function(module, exports) {
 
 	/** Used for built-in method references. */
 	var objectProto = Object.prototype;
 
 	/**
 	 * Used to resolve the
 	 * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
@@ -5101,17 +5392,17 @@ return /******/ (function(modules) { // 
 	function objectToString(value) {
 	  return nativeObjectToString.call(value);
 	}
 
 	module.exports = objectToString;
 
 
 /***/ },
-/* 63 */
+/* 64 */
 /***/ function(module, exports) {
 
 	/**
 	 * Checks if `value` is object-like. A value is object-like if it's not `null`
 	 * and has a `typeof` result of "object".
 	 *
 	 * @static
 	 * @memberOf _
@@ -5136,20 +5427,20 @@ return /******/ (function(modules) { // 
 	function isObjectLike(value) {
 	  return value != null && typeof value == 'object';
 	}
 
 	module.exports = isObjectLike;
 
 
 /***/ },
-/* 64 */
+/* 65 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var memoizeCapped = __webpack_require__(65);
+	var memoizeCapped = __webpack_require__(66);
 
 	/** Used to match property names within property paths. */
 	var reLeadingDot = /^\./,
 	    rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g;
 
 	/** Used to match backslashes in property paths. */
 	var reEscapeChar = /\\(\\)?/g;
 
@@ -5170,20 +5461,20 @@ return /******/ (function(modules) { // 
 	  });
 	  return result;
 	});
 
 	module.exports = stringToPath;
 
 
 /***/ },
-/* 65 */
+/* 66 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var memoize = __webpack_require__(66);
+	var memoize = __webpack_require__(67);
 
 	/** Used as the maximum memoize cache size. */
 	var MAX_MEMOIZE_SIZE = 500;
 
 	/**
 	 * A specialized version of `_.memoize` which clears the memoized function's
 	 * cache when it exceeds `MAX_MEMOIZE_SIZE`.
 	 *
@@ -5202,20 +5493,20 @@ return /******/ (function(modules) { // 
 	  var cache = result.cache;
 	  return result;
 	}
 
 	module.exports = memoizeCapped;
 
 
 /***/ },
-/* 66 */
+/* 67 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var MapCache = __webpack_require__(67);
+	var MapCache = __webpack_require__(68);
 
 	/** Error message constants. */
 	var FUNC_ERROR_TEXT = 'Expected a function';
 
 	/**
 	 * Creates a function that memoizes the result of `func`. If `resolver` is
 	 * provided, it determines the cache key for storing the result based on the
 	 * arguments provided to the memoized function. By default, the first argument
@@ -5281,24 +5572,24 @@ return /******/ (function(modules) { // 
 
 	// Expose `MapCache`.
 	memoize.Cache = MapCache;
 
 	module.exports = memoize;
 
 
 /***/ },
-/* 67 */
+/* 68 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var mapCacheClear = __webpack_require__(68),
-	    mapCacheDelete = __webpack_require__(93),
-	    mapCacheGet = __webpack_require__(96),
-	    mapCacheHas = __webpack_require__(97),
-	    mapCacheSet = __webpack_require__(98);
+	var mapCacheClear = __webpack_require__(69),
+	    mapCacheDelete = __webpack_require__(94),
+	    mapCacheGet = __webpack_require__(97),
+	    mapCacheHas = __webpack_require__(98),
+	    mapCacheSet = __webpack_require__(99);
 
 	/**
 	 * Creates a map cache object to store key-value pairs.
 	 *
 	 * @private
 	 * @constructor
 	 * @param {Array} [entries] The key-value pairs to cache.
 	 */
@@ -5319,22 +5610,22 @@ return /******/ (function(modules) { // 
 	MapCache.prototype.get = mapCacheGet;
 	MapCache.prototype.has = mapCacheHas;
 	MapCache.prototype.set = mapCacheSet;
 
 	module.exports = MapCache;
 
 
 /***/ },
-/* 68 */
+/* 69 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var Hash = __webpack_require__(69),
-	    ListCache = __webpack_require__(84),
-	    Map = __webpack_require__(92);
+	var Hash = __webpack_require__(70),
+	    ListCache = __webpack_require__(85),
+	    Map = __webpack_require__(93);
 
 	/**
 	 * Removes all key-value entries from the map.
 	 *
 	 * @private
 	 * @name clear
 	 * @memberOf MapCache
 	 */
@@ -5346,24 +5637,24 @@ return /******/ (function(modules) { // 
 	    'string': new Hash
 	  };
 	}
 
 	module.exports = mapCacheClear;
 
 
 /***/ },
-/* 69 */
+/* 70 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var hashClear = __webpack_require__(70),
-	    hashDelete = __webpack_require__(80),
-	    hashGet = __webpack_require__(81),
-	    hashHas = __webpack_require__(82),
-	    hashSet = __webpack_require__(83);
+	var hashClear = __webpack_require__(71),
+	    hashDelete = __webpack_require__(81),
+	    hashGet = __webpack_require__(82),
+	    hashHas = __webpack_require__(83),
+	    hashSet = __webpack_require__(84);
 
 	/**
 	 * Creates a hash object.
 	 *
 	 * @private
 	 * @constructor
 	 * @param {Array} [entries] The key-value pairs to cache.
 	 */
@@ -5384,20 +5675,20 @@ return /******/ (function(modules) { // 
 	Hash.prototype.get = hashGet;
 	Hash.prototype.has = hashHas;
 	Hash.prototype.set = hashSet;
 
 	module.exports = Hash;
 
 
 /***/ },
-/* 70 */
+/* 71 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var nativeCreate = __webpack_require__(71);
+	var nativeCreate = __webpack_require__(72);
 
 	/**
 	 * Removes all key-value entries from the hash.
 	 *
 	 * @private
 	 * @name clear
 	 * @memberOf Hash
 	 */
@@ -5405,33 +5696,33 @@ return /******/ (function(modules) { // 
 	  this.__data__ = nativeCreate ? nativeCreate(null) : {};
 	  this.size = 0;
 	}
 
 	module.exports = hashClear;
 
 
 /***/ },
-/* 71 */
+/* 72 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var getNative = __webpack_require__(72);
+	var getNative = __webpack_require__(73);
 
 	/* Built-in method references that are verified to be native. */
 	var nativeCreate = getNative(Object, 'create');
 
 	module.exports = nativeCreate;
 
 
 /***/ },
-/* 72 */
+/* 73 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var baseIsNative = __webpack_require__(73),
-	    getValue = __webpack_require__(79);
+	var baseIsNative = __webpack_require__(74),
+	    getValue = __webpack_require__(80);
 
 	/**
 	 * Gets the native function at `key` of `object`.
 	 *
 	 * @private
 	 * @param {Object} object The object to query.
 	 * @param {string} key The key of the method to get.
 	 * @returns {*} Returns the function if it's native, else `undefined`.
@@ -5440,23 +5731,23 @@ return /******/ (function(modules) { // 
 	  var value = getValue(object, key);
 	  return baseIsNative(value) ? value : undefined;
 	}
 
 	module.exports = getNative;
 
 
 /***/ },
-/* 73 */
+/* 74 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var isFunction = __webpack_require__(74),
-	    isMasked = __webpack_require__(76),
-	    isObject = __webpack_require__(75),
-	    toSource = __webpack_require__(78);
+	var isFunction = __webpack_require__(75),
+	    isMasked = __webpack_require__(77),
+	    isObject = __webpack_require__(76),
+	    toSource = __webpack_require__(79);
 
 	/**
 	 * Used to match `RegExp`
 	 * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns).
 	 */
 	var reRegExpChar = /[\\^$.*+?()[\]{}|]/g;
 
 	/** Used to detect host constructors (Safari). */
@@ -5493,21 +5784,21 @@ return /******/ (function(modules) { // 
 	  var pattern = isFunction(value) ? reIsNative : reIsHostCtor;
 	  return pattern.test(toSource(value));
 	}
 
 	module.exports = baseIsNative;
 
 
 /***/ },
-/* 74 */
+/* 75 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var baseGetTag = __webpack_require__(57),
-	    isObject = __webpack_require__(75);
+	var baseGetTag = __webpack_require__(58),
+	    isObject = __webpack_require__(76);
 
 	/** `Object#toString` result references. */
 	var asyncTag = '[object AsyncFunction]',
 	    funcTag = '[object Function]',
 	    genTag = '[object GeneratorFunction]',
 	    proxyTag = '[object Proxy]';
 
 	/**
@@ -5536,17 +5827,17 @@ return /******/ (function(modules) { // 
 	  var tag = baseGetTag(value);
 	  return tag == funcTag || tag == genTag || tag == asyncTag || tag == proxyTag;
 	}
 
 	module.exports = isFunction;
 
 
 /***/ },
-/* 75 */
+/* 76 */
 /***/ function(module, exports) {
 
 	/**
 	 * Checks if `value` is the
 	 * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
 	 * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
 	 *
 	 * @static
@@ -5573,20 +5864,20 @@ return /******/ (function(modules) { // 
 	  var type = typeof value;
 	  return value != null && (type == 'object' || type == 'function');
 	}
 
 	module.exports = isObject;
 
 
 /***/ },
-/* 76 */
+/* 77 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var coreJsData = __webpack_require__(77);
+	var coreJsData = __webpack_require__(78);
 
 	/** Used to detect methods masquerading as native. */
 	var maskSrcKey = (function() {
 	  var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || '');
 	  return uid ? ('Symbol(src)_1.' + uid) : '';
 	}());
 
 	/**
@@ -5599,29 +5890,29 @@ return /******/ (function(modules) { // 
 	function isMasked(func) {
 	  return !!maskSrcKey && (maskSrcKey in func);
 	}
 
 	module.exports = isMasked;
 
 
 /***/ },
-/* 77 */
+/* 78 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var root = __webpack_require__(59);
+	var root = __webpack_require__(60);
 
 	/** Used to detect overreaching core-js shims. */
 	var coreJsData = root['__core-js_shared__'];
 
 	module.exports = coreJsData;
 
 
 /***/ },
-/* 78 */
+/* 79 */
 /***/ function(module, exports) {
 
 	/** Used for built-in method references. */
 	var funcProto = Function.prototype;
 
 	/** Used to resolve the decompiled source of functions. */
 	var funcToString = funcProto.toString;
 
@@ -5643,17 +5934,17 @@ return /******/ (function(modules) { // 
 	  }
 	  return '';
 	}
 
 	module.exports = toSource;
 
 
 /***/ },
-/* 79 */
+/* 80 */
 /***/ function(module, exports) {
 
 	/**
 	 * Gets the value at `key` of `object`.
 	 *
 	 * @private
 	 * @param {Object} [object] The object to query.
 	 * @param {string} key The key of the property to get.
@@ -5662,17 +5953,17 @@ return /******/ (function(modules) { // 
 	function getValue(object, key) {
 	  return object == null ? undefined : object[key];
 	}
 
 	module.exports = getValue;
 
 
 /***/ },
-/* 80 */
+/* 81 */
 /***/ function(module, exports) {
 
 	/**
 	 * Removes `key` and its value from the hash.
 	 *
 	 * @private
 	 * @name delete
 	 * @memberOf Hash
@@ -5685,20 +5976,20 @@ return /******/ (function(modules) { // 
 	  this.size -= result ? 1 : 0;
 	  return result;
 	}
 
 	module.exports = hashDelete;
 
 
 /***/ },
-/* 81 */
+/* 82 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var nativeCreate = __webpack_require__(71);
+	var nativeCreate = __webpack_require__(72);
 
 	/** Used to stand-in for `undefined` hash values. */
 	var HASH_UNDEFINED = '__lodash_hash_undefined__';
 
 	/** Used for built-in method references. */
 	var objectProto = Object.prototype;
 
 	/** Used to check objects for own properties. */
@@ -5721,20 +6012,20 @@ return /******/ (function(modules) { // 
 	  }
 	  return hasOwnProperty.call(data, key) ? data[key] : undefined;
 	}
 
 	module.exports = hashGet;
 
 
 /***/ },
-/* 82 */
+/* 83 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var nativeCreate = __webpack_require__(71);
+	var nativeCreate = __webpack_require__(72);
 
 	/** Used for built-in method references. */
 	var objectProto = Object.prototype;
 
 	/** Used to check objects for own properties. */
 	var hasOwnProperty = objectProto.hasOwnProperty;
 
 	/**
@@ -5750,20 +6041,20 @@ return /******/ (function(modules) { // 
 	  var data = this.__data__;
 	  return nativeCreate ? (data[key] !== undefined) : hasOwnProperty.call(data, key);
 	}
 
 	module.exports = hashHas;
 
 
 /***/ },
-/* 83 */
+/* 84 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var nativeCreate = __webpack_require__(71);
+	var nativeCreate = __webpack_require__(72);
 
 	/** Used to stand-in for `undefined` hash values. */
 	var HASH_UNDEFINED = '__lodash_hash_undefined__';
 
 	/**
 	 * Sets the hash `key` to `value`.
 	 *
 	 * @private
@@ -5779,24 +6070,24 @@ return /******/ (function(modules) { // 
 	  data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value;
 	  return this;
 	}
 
 	module.exports = hashSet;
 
 
 /***/ },
-/* 84 */
+/* 85 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var listCacheClear = __webpack_require__(85),
-	    listCacheDelete = __webpack_require__(86),
-	    listCacheGet = __webpack_require__(89),
-	    listCacheHas = __webpack_require__(90),
-	    listCacheSet = __webpack_require__(91);
+	var listCacheClear = __webpack_require__(86),
+	    listCacheDelete = __webpack_require__(87),
+	    listCacheGet = __webpack_require__(90),
+	    listCacheHas = __webpack_require__(91),
+	    listCacheSet = __webpack_require__(92);
 
 	/**
 	 * Creates an list cache object.
 	 *
 	 * @private
 	 * @constructor
 	 * @param {Array} [entries] The key-value pairs to cache.
 	 */
@@ -5817,17 +6108,17 @@ return /******/ (function(modules) { // 
 	ListCache.prototype.get = listCacheGet;
 	ListCache.prototype.has = listCacheHas;
 	ListCache.prototype.set = listCacheSet;
 
 	module.exports = ListCache;
 
 
 /***/ },
-/* 85 */
+/* 86 */
 /***/ function(module, exports) {
 
 	/**
 	 * Removes all key-value entries from the list cache.
 	 *
 	 * @private
 	 * @name clear
 	 * @memberOf ListCache
@@ -5836,20 +6127,20 @@ return /******/ (function(modules) { // 
 	  this.__data__ = [];
 	  this.size = 0;
 	}
 
 	module.exports = listCacheClear;
 
 
 /***/ },
-/* 86 */
+/* 87 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var assocIndexOf = __webpack_require__(87);
+	var assocIndexOf = __webpack_require__(88);
 
 	/** Used for built-in method references. */
 	var arrayProto = Array.prototype;
 
 	/** Built-in value references. */
 	var splice = arrayProto.splice;
 
 	/**
@@ -5877,20 +6168,20 @@ return /******/ (function(modules) { // 
 	  --this.size;
 	  return true;
 	}
 
 	module.exports = listCacheDelete;
 
 
 /***/ },
-/* 87 */
+/* 88 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var eq = __webpack_require__(88);
+	var eq = __webpack_require__(89);
 
 	/**
 	 * Gets the index at which the `key` is found in `array` of key-value pairs.
 	 *
 	 * @private
 	 * @param {Array} array The array to inspect.
 	 * @param {*} key The key to search for.
 	 * @returns {number} Returns the index of the matched value, else `-1`.
@@ -5904,17 +6195,17 @@ return /******/ (function(modules) { // 
 	  }
 	  return -1;
 	}
 
 	module.exports = assocIndexOf;
 
 
 /***/ },
-/* 88 */
+/* 89 */
 /***/ function(module, exports) {
 
 	/**
 	 * Performs a
 	 * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
 	 * comparison between two values to determine if they are equivalent.
 	 *
 	 * @static
@@ -5947,20 +6238,20 @@ return /******/ (function(modules) { // 
 	function eq(value, other) {
 	  return value === other || (value !== value && other !== other);
 	}
 
 	module.exports = eq;
 
 
 /***/ },
-/* 89 */
+/* 90 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var assocIndexOf = __webpack_require__(87);
+	var assocIndexOf = __webpack_require__(88);
 
 	/**
 	 * Gets the list cache value for `key`.
 	 *
 	 * @private
 	 * @name get
 	 * @memberOf ListCache
 	 * @param {string} key The key of the value to get.
@@ -5972,20 +6263,20 @@ return /******/ (function(modules) { // 
 
 	  return index < 0 ? undefined : data[index][1];
 	}
 
 	module.exports = listCacheGet;
 
 
 /***/ },
-/* 90 */
+/* 91 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var assocIndexOf = __webpack_require__(87);
+	var assocIndexOf = __webpack_require__(88);
 
 	/**
 	 * Checks if a list cache value for `key` exists.
 	 *
 	 * @private
 	 * @name has
 	 * @memberOf ListCache
 	 * @param {string} key The key of the entry to check.
@@ -5994,20 +6285,20 @@ return /******/ (function(modules) { // 
 	function listCacheHas(key) {
 	  return assocIndexOf(this.__data__, key) > -1;
 	}
 
 	module.exports = listCacheHas;
 
 
 /***/ },
-/* 91 */
+/* 92 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var assocIndexOf = __webpack_require__(87);
+	var assocIndexOf = __webpack_require__(88);
 
 	/**
 	 * Sets the list cache `key` to `value`.
 	 *
 	 * @private
 	 * @name set
 	 * @memberOf ListCache
 	 * @param {string} key The key of the value to set.
@@ -6026,33 +6317,33 @@ return /******/ (function(modules) { // 
 	  }
 	  return this;
 	}
 
 	module.exports = listCacheSet;
 
 
 /***/ },
-/* 92 */
+/* 93 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var getNative = __webpack_require__(72),
-	    root = __webpack_require__(59);
+	var getNative = __webpack_require__(73),
+	    root = __webpack_require__(60);
 
 	/* Built-in method references that are verified to be native. */
 	var Map = getNative(root, 'Map');
 
 	module.exports = Map;
 
 
 /***/ },
-/* 93 */
+/* 94 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var getMapData = __webpack_require__(94);
+	var getMapData = __webpack_require__(95);
 
 	/**
 	 * Removes `key` and its value from the map.
 	 *
 	 * @private
 	 * @name delete
 	 * @memberOf MapCache
 	 * @param {string} key The key of the value to remove.
@@ -6063,20 +6354,20 @@ return /******/ (function(modules) { // 
 	  this.size -= result ? 1 : 0;
 	  return result;
 	}
 
 	module.exports = mapCacheDelete;
 
 
 /***/ },
-/* 94 */
+/* 95 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var isKeyable = __webpack_require__(95);
+	var isKeyable = __webpack_require__(96);
 
 	/**
 	 * Gets the data for `map`.
 	 *
 	 * @private
 	 * @param {Object} map The map to query.
 	 * @param {string} key The reference key.
 	 * @returns {*} Returns the map data.
@@ -6087,17 +6378,17 @@ return /******/ (function(modules) { // 
 	    ? data[typeof key == 'string' ? 'string' : 'hash']
 	    : data.map;
 	}
 
 	module.exports = getMapData;
 
 
 /***/ },
-/* 95 */
+/* 96 */
 /***/ function(module, exports) {
 
 	/**
 	 * Checks if `value` is suitable for use as unique object key.
 	 *
 	 * @private
 	 * @param {*} value The value to check.
 	 * @returns {boolean} Returns `true` if `value` is suitable, else `false`.
@@ -6108,20 +6399,20 @@ return /******/ (function(modules) { // 
 	    ? (value !== '__proto__')
 	    : (value === null);
 	}
 
 	module.exports = isKeyable;
 
 
 /***/ },
-/* 96 */
+/* 97 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var getMapData = __webpack_require__(94);
+	var getMapData = __webpack_require__(95);
 
 	/**
 	 * Gets the map value for `key`.
 	 *
 	 * @private
 	 * @name get
 	 * @memberOf MapCache
 	 * @param {string} key The key of the value to get.
@@ -6130,20 +6421,20 @@ return /******/ (function(modules) { // 
 	function mapCacheGet(key) {
 	  return getMapData(this, key).get(key);
 	}
 
 	module.exports = mapCacheGet;
 
 
 /***/ },
-/* 97 */
+/* 98 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var getMapData = __webpack_require__(94);
+	var getMapData = __webpack_require__(95);
 
 	/**
 	 * Checks if a map value for `key` exists.
 	 *
 	 * @private
 	 * @name has
 	 * @memberOf MapCache
 	 * @param {string} key The key of the entry to check.
@@ -6152,20 +6443,20 @@ return /******/ (function(modules) { // 
 	function mapCacheHas(key) {
 	  return getMapData(this, key).has(key);
 	}
 
 	module.exports = mapCacheHas;
 
 
 /***/ },
-/* 98 */
+/* 99 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var getMapData = __webpack_require__(94);
+	var getMapData = __webpack_require__(95);
 
 	/**
 	 * Sets the map `key` to `value`.
 	 *
 	 * @private
 	 * @name set
 	 * @memberOf MapCache
 	 * @param {string} key The key of the value to set.
@@ -6180,20 +6471,20 @@ return /******/ (function(modules) { // 
 	  this.size += data.size == size ? 0 : 1;
 	  return this;
 	}
 
 	module.exports = mapCacheSet;
 
 
 /***/ },
-/* 99 */
+/* 100 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var baseToString = __webpack_require__(100);
+	var baseToString = __webpack_require__(101);
 
 	/**
 	 * Converts `value` to a string. An empty string is returned for `null`
 	 * and `undefined` values. The sign of `-0` is preserved.
 	 *
 	 * @static
 	 * @memberOf _
 	 * @since 4.0.0
@@ -6214,23 +6505,23 @@ return /******/ (function(modules) { // 
 	function toString(value) {
 	  return value == null ? '' : baseToString(value);
 	}
 
 	module.exports = toString;
 
 
 /***/ },
-/* 100 */
+/* 101 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var Symbol = __webpack_require__(58),
-	    arrayMap = __webpack_require__(101),
-	    isArray = __webpack_require__(54),
-	    isSymbol = __webpack_require__(56);
+	var Symbol = __webpack_require__(59),
+	    arrayMap = __webpack_require__(102),
+	    isArray = __webpack_require__(55),
+	    isSymbol = __webpack_require__(57);
 
 	/** Used as references for various `Number` constants. */
 	var INFINITY = 1 / 0;
 
 	/** Used to convert symbols to primitives and strings. */
 	var symbolProto = Symbol ? Symbol.prototype : undefined,
 	    symbolToString = symbolProto ? symbolProto.toString : undefined;
 
@@ -6257,17 +6548,17 @@ return /******/ (function(modules) { // 
 	  var result = (value + '');
 	  return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
 	}
 
 	module.exports = baseToString;
 
 
 /***/ },
-/* 101 */
+/* 102 */
 /***/ function(module, exports) {
 
 	/**
 	 * A specialized version of `_.map` for arrays without support for iteratee
 	 * shorthands.
 	 *
 	 * @private
 	 * @param {Array} [array] The array to iterate over.
@@ -6284,20 +6575,20 @@ return /******/ (function(modules) { // 
 	  }
 	  return result;
 	}
 
 	module.exports = arrayMap;
 
 
 /***/ },
-/* 102 */
+/* 103 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var isSymbol = __webpack_require__(56);
+	var isSymbol = __webpack_require__(57);
 
 	/** Used as references for various `Number` constants. */
 	var INFINITY = 1 / 0;
 
 	/**
 	 * Converts `value` to a string key if it's not a string or symbol.
 	 *
 	 * @private
@@ -6311,21 +6602,21 @@ return /******/ (function(modules) { // 
 	  var result = (value + '');
 	  return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
 	}
 
 	module.exports = toKey;
 
 
 /***/ },
-/* 103 */
+/* 104 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var baseHas = __webpack_require__(104),
-	    hasPath = __webpack_require__(105);
+	var baseHas = __webpack_require__(105),
+	    hasPath = __webpack_require__(106);
 
 	/**
 	 * Checks if `path` is a direct property of `object`.
 	 *
 	 * @static
 	 * @since 0.1.0
 	 * @memberOf _
 	 * @category Object
@@ -6352,17 +6643,17 @@ return /******/ (function(modules) { // 
 	function has(object, path) {
 	  return object != null && hasPath(object, path, baseHas);
 	}
 
 	module.exports = has;
 
 
 /***/ },
-/* 104 */
+/* 105 */
 /***/ function(module, exports) {
 
 	/** Used for built-in method references. */
 	var objectProto = Object.prototype;
 
 	/** Used to check objects for own properties. */
 	var hasOwnProperty = objectProto.hasOwnProperty;
 
@@ -6377,25 +6668,25 @@ return /******/ (function(modules) { // 
 	function baseHas(object, key) {
 	  return object != null && hasOwnProperty.call(object, key);
 	}
 
 	module.exports = baseHas;
 
 
 /***/ },
-/* 105 */
+/* 106 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var castPath = __webpack_require__(53),
-	    isArguments = __webpack_require__(106),
-	    isArray = __webpack_require__(54),
-	    isIndex = __webpack_require__(108),
-	    isLength = __webpack_require__(109),
-	    toKey = __webpack_require__(102);
+	var castPath = __webpack_require__(54),
+	    isArguments = __webpack_require__(107),
+	    isArray = __webpack_require__(55),
+	    isIndex = __webpack_require__(109),
+	    isLength = __webpack_require__(110),
+	    toKey = __webpack_require__(103);
 
 	/**
 	 * Checks if `path` exists on `object`.
 	 *
 	 * @private
 	 * @param {Object} object The object to query.
 	 * @param {Array|string} path The path to check.
 	 * @param {Function} hasFunc The function to check properties.
@@ -6422,21 +6713,21 @@ return /******/ (function(modules) { // 
 	  return !!length && isLength(length) && isIndex(key, length) &&
 	    (isArray(object) || isArguments(object));
 	}
 
 	module.exports = hasPath;
 
 
 /***/ },
-/* 106 */
+/* 107 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var baseIsArguments = __webpack_require__(107),
-	    isObjectLike = __webpack_require__(63);
+	var baseIsArguments = __webpack_require__(108),
+	    isObjectLike = __webpack_require__(64);
 
 	/** Used for built-in method references. */
 	var objectProto = Object.prototype;
 
 	/** Used to check objects for own properties. */
 	var hasOwnProperty = objectProto.hasOwnProperty;
 
 	/** Built-in value references. */
@@ -6464,21 +6755,21 @@ return /******/ (function(modules) { // 
 	  return isObjectLike(value) && hasOwnProperty.call(value, 'callee') &&
 	    !propertyIsEnumerable.call(value, 'callee');
 	};
 
 	module.exports = isArguments;
 
 
 /***/ },
-/* 107 */
+/* 108 */
 /***/ function(module, exports, __webpack_require__) {
 
-	var baseGetTag = __webpack_require__(57),
-	    isObjectLike = __webpack_require__(63);
+	var baseGetTag = __webpack_require__(58),
+	    isObjectLike = __webpack_require__(64);
 
 	/** `Object#toString` result references. */
 	var argsTag = '[object Arguments]';
 
 	/**
 	 * The base implementation of `_.isArguments`.
 	 *
 	 * @private
@@ -6488,17 +6779,17 @@ return /******/ (function(modules) { // 
 	function baseIsArguments(value) {
 	  return isObjectLike(value) && baseGetTag(value) == argsTag;
 	}
 
 	module.exports = baseIsArguments;
 
 
 /***/ },
-/* 108 */
+/* 109 */
 /***/ function(module, exports) {
 
 	/** Used as references for various `Number` constants. */
 	var MAX_SAFE_INTEGER = 9007199254740991;
 
 	/** Used to detect unsigned integer values. */
 	var reIsUint = /^(?:0|[1-9]\d*)$/;
 
@@ -6516,17 +6807,17 @@ return /******/ (function(modules) { // 
 	    (typeof value == 'number' || reIsUint.test(value)) &&
 	    (value > -1 && value % 1 == 0 && value < length);
 	}
 
 	module.exports = isIndex;
 
 
 /***/ },
-/* 109 */
+/* 110 */
 /***/ function(module, exports) {
 
 	/** Used as references for various `Number` constants. */
 	var MAX_SAFE_INTEGER = 9007199254740991;
 
 	/**
 	 * Checks if `value` is a valid array-like length.
 	 *
--- a/devtools/client/webconsole/new-console-output/actions/messages.js
+++ b/devtools/client/webconsole/new-console-output/actions/messages.js
@@ -16,16 +16,17 @@ const {
   MESSAGE_ADD,
   NETWORK_MESSAGE_UPDATE,
   MESSAGES_CLEAR,
   MESSAGE_OPEN,
   MESSAGE_CLOSE,
   MESSAGE_TYPE,
   MESSAGE_TABLE_RECEIVE,
   MESSAGE_OBJECT_PROPERTIES_RECEIVE,
+  MESSAGE_OBJECT_ENTRIES_RECEIVE,
 } = require("../constants");
 
 const defaultIdGenerator = new IdGenerator();
 
 function messageAdd(packet, idGenerator = null) {
   if (idGenerator == null) {
     idGenerator = defaultIdGenerator;
   }
@@ -121,16 +122,28 @@ function networkMessageUpdate(packet, id
  */
 function messageObjectPropertiesLoad(id, client, grip) {
   return async (dispatch) => {
     const response = await client.getPrototypeAndProperties();
     dispatch(messageObjectPropertiesReceive(id, grip.actor, response));
   };
 }
 
+function messageObjectEntriesLoad(id, client, grip) {
+  return (dispatch) => {
+    client.enumEntries(enumResponse => {
+      const {iterator} = enumResponse;
+      iterator.slice(0, iterator.count, sliceResponse => {
+        console.log("sliceResponse", sliceResponse);
+        dispatch(messageObjectEntriesReceive(id, grip.actor, sliceResponse));
+      });
+    });
+  }
+}
+
 /**
  * This action is dispatched when properties of a grip are loaded.
  *
  * @param {string} id - The message id the grip is in.
  * @param {string} actor - The actor id of the grip the properties were loaded from.
  * @param {object} properties - A RDP packet that contains the properties of the grip.
  * @returns {object}
  */
@@ -138,21 +151,41 @@ function messageObjectPropertiesReceive(
   return {
     type: MESSAGE_OBJECT_PROPERTIES_RECEIVE,
     id,
     actor,
     properties
   };
 }
 
+/**
+ * This action is dispatched when entries of a grip are loaded.
+ *
+ * @param {string} id - The message id the grip is in.
+ * @param {string} actor - The actor id of the grip the properties were loaded from.
+ * @param {object} entries - A RDP packet that contains the entries of the grip.
+ * @returns {object}
+ */
+function messageObjectEntriesReceive(id, actor, entries) {
+console.log("messageObjectEntriesReceive", entries);
+  return {
+    type: MESSAGE_OBJECT_ENTRIES_RECEIVE,
+    id,
+    actor,
+    entries
+  };
+}
+
 module.exports = {
   messageAdd,
   messagesClear,
   messageOpen,
   messageClose,
   messageTableDataGet,
   networkMessageUpdate,
   messageObjectPropertiesLoad,
+  messageObjectEntriesLoad,
   // for test purpose only.
   messageTableDataReceive,
   messageObjectPropertiesReceive,
+  messageObjectEntriesReceive,
 };
 
--- a/devtools/client/webconsole/new-console-output/components/console-output.js
+++ b/devtools/client/webconsole/new-console-output/components/console-output.js
@@ -11,16 +11,17 @@ const {
 } = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 
 const {
   getAllMessagesById,
   getAllMessagesUiById,
   getAllMessagesTableDataById,
   getAllMessagesObjectPropertiesById,
+  getAllMessagesObjectEntriesById,
   getAllNetworkMessagesUpdateById,
   getVisibleMessages,
   getAllRepeatById,
 } = require("devtools/client/webconsole/new-console-output/selectors/messages");
 const MessageContainer = createFactory(require("devtools/client/webconsole/new-console-output/components/message-container").MessageContainer);
 
 const ConsoleOutput = createClass({
 
@@ -33,16 +34,17 @@ const ConsoleOutput = createClass({
       attachRefToHud: PropTypes.func.isRequired,
       openContextMenu: PropTypes.func.isRequired,
       sourceMapService: PropTypes.object,
     }),
     dispatch: PropTypes.func.isRequired,
     timestampsVisible: PropTypes.bool,
     messagesTableData: PropTypes.object.isRequired,
     messagesObjectProperties: PropTypes.object.isRequired,
+    messagesObjectEntries: PropTypes.object.isRequired,
     messagesRepeat: PropTypes.object.isRequired,
     networkMessagesUpdate: PropTypes.object.isRequired,
     visibleMessages: PropTypes.array.isRequired,
   },
 
   componentDidMount() {
     // Do the scrolling in the nextTick since this could hit console startup performances.
     // See https://bugzilla.mozilla.org/show_bug.cgi?id=1355869
@@ -80,16 +82,17 @@ const ConsoleOutput = createClass({
   render() {
     let {
       dispatch,
       visibleMessages,
       messages,
       messagesUi,
       messagesTableData,
       messagesObjectProperties,
+      messagesObjectEntries,
       messagesRepeat,
       networkMessagesUpdate,
       serviceContainer,
       timestampsVisible,
     } = this.props;
 
     let messageNodes = visibleMessages.map((messageId) => MessageContainer({
       dispatch,
@@ -98,16 +101,17 @@ const ConsoleOutput = createClass({
       serviceContainer,
       open: messagesUi.includes(messageId),
       tableData: messagesTableData.get(messageId),
       timestampsVisible,
       repeat: messagesRepeat[messageId],
       networkMessageUpdate: networkMessagesUpdate[messageId],
       getMessage: () => messages.get(messageId),
       loadedObjectProperties: messagesObjectProperties.get(messageId),
+      loadedObjectEntries: messagesObjectEntries.get(messageId),
     }));
 
     return (
       dom.div({
         className: "webconsole-output",
         onContextMenu: this.onContextMenu,
         ref: node => {
           this.outputNode = node;
@@ -131,15 +135,16 @@ function isScrolledToBottom(outputNode, 
 
 function mapStateToProps(state, props) {
   return {
     messages: getAllMessagesById(state),
     visibleMessages: getVisibleMessages(state),
     messagesUi: getAllMessagesUiById(state),
     messagesTableData: getAllMessagesTableDataById(state),
     messagesObjectProperties: getAllMessagesObjectPropertiesById(state),
+    messagesObjectEntries: getAllMessagesObjectEntriesById(state),
     messagesRepeat: getAllRepeatById(state),
     networkMessagesUpdate: getAllNetworkMessagesUpdateById(state),
     timestampsVisible: state.ui.timestampsVisible,
   };
 }
 
 module.exports = connect(mapStateToProps)(ConsoleOutput);
--- a/devtools/client/webconsole/new-console-output/components/grip-message-body.js
+++ b/devtools/client/webconsole/new-console-output/components/grip-message-body.js
@@ -40,16 +40,17 @@ GripMessageBody.propTypes = {
   serviceContainer: PropTypes.shape({
     createElement: PropTypes.func.isRequired,
     hudProxyClient: PropTypes.object.isRequired,
   }),
   userProvidedStyle: PropTypes.string,
   useQuotes: PropTypes.bool,
   escapeWhitespace: PropTypes.bool,
   loadedObjectProperties: PropTypes.object,
+  loadedObjectEntries: PropTypes.object,
   type: PropTypes.string,
   helperType: PropTypes.string,
 };
 
 GripMessageBody.defaultProps = {
   mode: MODE.LONG,
 };
 
@@ -59,16 +60,17 @@ function GripMessageBody(props) {
     messageId,
     grip,
     userProvidedStyle,
     serviceContainer,
     useQuotes,
     escapeWhitespace,
     mode = MODE.LONG,
     loadedObjectProperties,
+    loadedObjectEntries,
   } = props;
 
   let styleObject;
   if (userProvidedStyle && userProvidedStyle !== "") {
     styleObject = cleanupStyle(userProvidedStyle, serviceContainer.createElement);
   }
 
   let onDOMNodeMouseOver;
@@ -101,16 +103,21 @@ function GripMessageBody(props) {
         value: grip
       }
     }],
     getObjectProperties: actor => loadedObjectProperties && loadedObjectProperties[actor],
     loadObjectProperties: object => {
       const client = new ObjectClient(serviceContainer.hudProxyClient, object);
       dispatch(actions.messageObjectPropertiesLoad(messageId, client, object));
     },
+    getObjectEntries: actor => loadedObjectEntries && loadedObjectEntries[actor],
+    loadObjectEntries: object => {
+      const client = new ObjectClient(serviceContainer.hudProxyClient, object);
+      dispatch(actions.messageObjectEntriesLoad(messageId, client, object));
+    },
   };
 
   if (typeof grip === "string" || grip.type === "longString") {
     Object.assign(objectInspectorProps, {
       useQuotes,
       escapeWhitespace,
       style: styleObject
     });
--- a/devtools/client/webconsole/new-console-output/components/message-container.js
+++ b/devtools/client/webconsole/new-console-output/components/message-container.js
@@ -34,16 +34,17 @@ const MessageContainer = createClass({
     open: PropTypes.bool.isRequired,
     serviceContainer: PropTypes.object.isRequired,
     tableData: PropTypes.object,
     timestampsVisible: PropTypes.bool.isRequired,
     repeat: PropTypes.number,
     networkMessageUpdate: PropTypes.object,
     getMessage: PropTypes.func.isRequired,
     loadedObjectProperties: PropTypes.object,
+    loadedObjectEntries: PropTypes.object,
   },
 
   getDefaultProps: function () {
     return {
       open: false,
     };
   },
 
@@ -52,23 +53,26 @@ const MessageContainer = createClass({
     const openChanged = this.props.open !== nextProps.open;
     const tableDataChanged = this.props.tableData !== nextProps.tableData;
     const timestampVisibleChanged =
       this.props.timestampsVisible !== nextProps.timestampsVisible;
     const networkMessageUpdateChanged =
       this.props.networkMessageUpdate !== nextProps.networkMessageUpdate;
     const loadedObjectPropertiesChanged =
       this.props.loadedObjectProperties !== nextProps.loadedObjectProperties;
+    const loadedObjectEntriesChanged =
+      this.props.loadedObjectEntries !== nextProps.loadedObjectEntries;
 
     return repeatChanged
       || openChanged
       || tableDataChanged
       || timestampVisibleChanged
       || networkMessageUpdateChanged
-      || loadedObjectPropertiesChanged;
+      || loadedObjectPropertiesChanged
+      || loadedObjectEntriesChanged;
   },
 
   render() {
     const message = this.props.getMessage();
 
     let MessageComponent = getMessageComponent(message);
     return MessageComponent(Object.assign({message}, this.props));
   }
--- a/devtools/client/webconsole/new-console-output/components/message-types/console-api-call.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/console-api-call.js
@@ -22,32 +22,34 @@ ConsoleApiCall.displayName = "ConsoleApi
 
 ConsoleApiCall.propTypes = {
   dispatch: PropTypes.func.isRequired,
   message: PropTypes.object.isRequired,
   open: PropTypes.bool,
   serviceContainer: PropTypes.object.isRequired,
   timestampsVisible: PropTypes.bool.isRequired,
   loadedObjectProperties: PropTypes.object,
+  loadedObjectEntries: PropTypes.object,
 };
 
 ConsoleApiCall.defaultProps = {
   open: false,
 };
 
 function ConsoleApiCall(props) {
   const {
     dispatch,
     message,
     open,
     tableData,
     serviceContainer,
     timestampsVisible,
     repeat,
     loadedObjectProperties,
+    loadedObjectEntries,
   } = props;
   const {
     id: messageId,
     indent,
     source,
     type,
     level,
     stacktrace,
@@ -57,16 +59,17 @@ function ConsoleApiCall(props) {
     messageText,
     userProvidedStyles,
   } = message;
 
   let messageBody;
   const messageBodyConfig = {
     dispatch,
     loadedObjectProperties,
+    loadedObjectEntries,
     messageId,
     parameters,
     userProvidedStyles,
     serviceContainer,
     type,
   };
 
   if (type === "trace") {
@@ -124,16 +127,17 @@ function ConsoleApiCall(props) {
     timestampsVisible,
   });
 }
 
 function formatReps(options = {}) {
   const {
     dispatch,
     loadedObjectProperties,
+    loadedObjectEntries,
     messageId,
     parameters,
     serviceContainer,
     userProvidedStyles,
     type,
   } = options;
 
   return (
@@ -143,16 +147,17 @@ function formatReps(options = {}) {
         dispatch,
         messageId,
         grip,
         key,
         userProvidedStyle: userProvidedStyles ? userProvidedStyles[key] : null,
         serviceContainer,
         useQuotes: false,
         loadedObjectProperties,
+        loadedObjectEntries,
         type,
       }))
       // Interleave spaces.
       .reduce((arr, v, i) => {
         return i + 1 < parameters.length
           ? arr.concat(v, dom.span({}, " "))
           : arr.concat(v);
       }, [])
--- a/devtools/client/webconsole/new-console-output/components/message-types/evaluation-result.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/evaluation-result.js
@@ -17,25 +17,27 @@ const GripMessageBody = require("devtool
 EvaluationResult.displayName = "EvaluationResult";
 
 EvaluationResult.propTypes = {
   dispatch: PropTypes.func.isRequired,
   message: PropTypes.object.isRequired,
   timestampsVisible: PropTypes.bool.isRequired,
   serviceContainer: PropTypes.object,
   loadedObjectProperties: PropTypes.object,
+  loadedObjectEntries: PropTypes.object,
 };
 
 function EvaluationResult(props) {
   const {
     dispatch,
     message,
     serviceContainer,
     timestampsVisible,
     loadedObjectProperties,
+    loadedObjectEntries,
   } = props;
 
   const {
     source,
     type,
     helperType,
     level,
     id: messageId,
@@ -61,16 +63,17 @@ function EvaluationResult(props) {
     messageBody = GripMessageBody({
       dispatch,
       messageId,
       grip: parameters,
       serviceContainer,
       useQuotes: true,
       escapeWhitespace: false,
       loadedObjectProperties,
+      loadedObjectEntries,
       type,
       helperType,
     });
   }
 
   const topLevelClasses = ["cm-s-mozilla"];
 
   return Message({
--- a/devtools/client/webconsole/new-console-output/constants.js
+++ b/devtools/client/webconsole/new-console-output/constants.js
@@ -9,16 +9,17 @@ const actionTypes = {
   BATCH_ACTIONS: "BATCH_ACTIONS",
   MESSAGE_ADD: "MESSAGE_ADD",
   MESSAGES_CLEAR: "MESSAGES_CLEAR",
   MESSAGE_OPEN: "MESSAGE_OPEN",
   MESSAGE_CLOSE: "MESSAGE_CLOSE",
   NETWORK_MESSAGE_UPDATE: "NETWORK_MESSAGE_UPDATE",
   MESSAGE_TABLE_RECEIVE: "MESSAGE_TABLE_RECEIVE",
   MESSAGE_OBJECT_PROPERTIES_RECEIVE: "MESSAGE_OBJECT_PROPERTIES_RECEIVE",
+  MESSAGE_OBJECT_ENTRIES_RECEIVE: "MESSAGE_OBJECT_ENTRIES_RECEIVE",
   REMOVED_ACTORS_CLEAR: "REMOVED_ACTORS_CLEAR",
   TIMESTAMPS_TOGGLE: "TIMESTAMPS_TOGGLE",
   FILTER_TOGGLE: "FILTER_TOGGLE",
   FILTER_TEXT_SET: "FILTER_TEXT_SET",
   FILTERS_CLEAR: "FILTERS_CLEAR",
   FILTER_BAR_TOGGLE: "FILTER_BAR_TOGGLE",
 };
 
--- a/devtools/client/webconsole/new-console-output/reducers/messages.js
+++ b/devtools/client/webconsole/new-console-output/reducers/messages.js
@@ -27,16 +27,21 @@ const MessageState = Immutable.Record({
   // Map of the form {messageId : tableData}, which represent the data passed
   // as an argument in console.table calls.
   messagesTableDataById: Immutable.Map(),
   // Map of the form {messageId : {[actor]: properties}}, where `properties` is
   // a RDP packet containing the properties of the ${actor} grip.
   // This map is consumed by the ObjectInspector so we only load properties once,
   // when needed (when an ObjectInspector node is expanded), and then caches them.
   messagesObjectPropertiesById: Immutable.Map(),
+  // Map of the form {messageId : {[actor]: entries}}, where `entries` is
+  // a RDP packet containing the entries of the ${actor} grip.
+  // This map is consumed by the ObjectInspector so we only load entries once,
+  // when needed (when an ObjectInspector node is expanded), and then caches them.
+  messagesObjectEntriesById: Immutable.Map(),
   // Map of the form {groupMessageId : groupArray},
   // where groupArray is the list of of all the parent groups' ids of the groupMessageId.
   groupsById: Immutable.Map(),
   // Message id of the current group (no corresponding console.groupEnd yet).
   currentGroup: null,
   // Array of removed actors (i.e. actors logged in removed messages) we keep track of
   // in order to properly release them.
   // This array is not supposed to be consumed by any UI component.
@@ -49,16 +54,17 @@ const MessageState = Immutable.Record({
 });
 
 function messages(state = new MessageState(), action, filtersState, prefsState) {
   const {
     messagesById,
     messagesUiById,
     messagesTableDataById,
     messagesObjectPropertiesById,
+    messagesObjectEntriesById,
     networkMessagesUpdateById,
     groupsById,
     currentGroup,
     repeatById,
     visibleMessages,
   } = state;
 
   const {logLimit} = prefsState;
@@ -202,16 +208,26 @@ function messages(state = new MessageSta
         "messagesObjectPropertiesById",
         messagesObjectPropertiesById.set(
           action.id,
           Object.assign({
             [action.actor]: action.properties
           }, messagesObjectPropertiesById.get(action.id))
         )
       );
+    case constants.MESSAGE_OBJECT_ENTRIES_RECEIVE:
+      return state.set(
+        "messagesObjectEntriesById",
+        messagesObjectEntriesById.set(
+          action.id,
+          Object.assign({
+            [action.actor]: action.entries
+          }, messagesObjectEntriesById.get(action.id))
+        )
+      );
 
     case constants.NETWORK_MESSAGE_UPDATE:
       return state.set(
         "networkMessagesUpdateById",
         Object.assign({}, networkMessagesUpdateById, {
           [action.message.id]: action.message
         })
       );
@@ -351,16 +367,20 @@ function limitTopLevelMessageCount(state
   }
   if (mapHasRemovedIdKey(record.groupsById)) {
     record.set("groupsById", record.groupsById.withMutations(cleanUpCollection));
   }
   if (mapHasRemovedIdKey(record.messagesObjectPropertiesById)) {
     record.set("messagesObjectPropertiesById",
       record.messagesObjectPropertiesById.withMutations(cleanUpCollection));
   }
+  if (mapHasRemovedIdKey(record.messagesObjectEntriesById)) {
+    record.set("messagesObjectEntriesById",
+      record.messagesObjectEntriesById.withMutations(cleanUpCollection));
+  }
   if (objectHasRemovedIdKey(record.repeatById)) {
     record.set("repeatById", cleanUpObject(record.repeatById));
   }
 
   if (objectHasRemovedIdKey(record.networkMessagesUpdateById)) {
     record.set("networkMessagesUpdateById",
       cleanUpObject(record.networkMessagesUpdateById));
   }
@@ -390,16 +410,21 @@ function getAllActorsInMessage(message, 
     return res;
   }, [])];
 
   const loadedProperties = state.messagesObjectPropertiesById.get(message.id);
   if (loadedProperties) {
     actors.push(...Object.keys(loadedProperties));
   }
 
+  const loadedEntries = state.messagesObjectEntriesById.get(message.id);
+  if (loadedEntries) {
+    actors.push(...Object.keys(loadedEntries));
+  }
+
   return actors;
 }
 
 /**
  * Returns total count of top level messages (those which are not
  * within a group).
  */
 function getToplevelMessageCount(record) {
--- a/devtools/client/webconsole/new-console-output/selectors/messages.js
+++ b/devtools/client/webconsole/new-console-output/selectors/messages.js
@@ -20,16 +20,20 @@ function getAllMessagesUiById(state) {
 function getAllMessagesTableDataById(state) {
   return state.messages.messagesTableDataById;
 }
 
 function getAllMessagesObjectPropertiesById(state) {
   return state.messages.messagesObjectPropertiesById;
 }
 
+function getAllMessagesObjectEntriesById(state) {
+  return state.messages.messagesObjectEntriesById;
+}
+
 function getAllGroupsById(state) {
   return state.messages.groupsById;
 }
 
 function getCurrentGroup(state) {
   return state.messages.currentGroup;
 }
 
@@ -51,9 +55,10 @@ module.exports = {
   getAllMessagesUiById,
   getAllMessagesTableDataById,
   getAllGroupsById,
   getCurrentGroup,
   getVisibleMessages,
   getAllRepeatById,
   getAllNetworkMessagesUpdateById,
   getAllMessagesObjectPropertiesById,
+  getAllMessagesObjectEntriesById,
 };
--- a/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/stub-snippets.js
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/stub-snippets.js
@@ -22,25 +22,31 @@ const consoleApiCommands = [
   "console.log('myregex', /a.b.c/)",
   "console.table(['red', 'green', 'blue']);",
   "console.log('myobject', {red: 'redValue', green: 'greenValue', blue: 'blueValue'});",
 ];
 
 let consoleApi = new Map(consoleApiCommands.map(
   cmd => [cmd, {keys: [cmd], code: cmd}]));
 
-consoleApi.set("console.map('mymap')", {
-  keys: ["console.map('mymap')"],
+consoleApi.set("console.log('mymap')", {
+  keys: ["console.log('mymap')"],
   code: `
 var map = new Map();
 map.set("key1", "value1");
 map.set("key2", "value2");
 console.log('mymap', map);
 `});
 
+consoleApi.set("console.log('myset')", {
+  keys: ["console.log('myset')"],
+  code: `
+console.log('myset', new Set(["a", "b"]));
+`});
+
 consoleApi.set("console.trace()", {
   keys: ["console.trace()"],
   code: `
 function testStacktraceFiltering() {
   console.trace()
 }
 function foo() {
   testStacktraceFiltering()
--- a/devtools/client/webconsole/new-console-output/test/fixtures/stubs/consoleApi.js
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stubs/consoleApi.js
@@ -553,30 +553,30 @@ stubPreparedMessages.set("console.log('m
   },
   "groupId": null,
   "exceptionDocURL": null,
   "userProvidedStyles": [],
   "notes": null,
   "indent": 0
 }));
 
-stubPreparedMessages.set("console.map('mymap')", new ConsoleMessage({
+stubPreparedMessages.set("console.log('mymap')", new ConsoleMessage({
   "id": "1",
   "allowRepeating": true,
   "source": "console-api",
-  "timeStamp": 1493125410207,
+  "timeStamp": 1501506737042,
   "type": "log",
   "helperType": null,
   "level": "log",
   "messageText": null,
   "parameters": [
     "mymap",
     {
       "type": "object",
-      "actor": "server1.conn0.child1/obj36",
+      "actor": "server1.conn0.child1/obj37",
       "class": "Map",
       "extensible": true,
       "frozen": false,
       "sealed": false,
       "ownPropertyLength": 0,
       "preview": {
         "kind": "MapLike",
         "size": 2,
@@ -588,30 +588,73 @@ stubPreparedMessages.set("console.map('m
           [
             "key2",
             "value2"
           ]
         ]
       }
     }
   ],
-  "repeatId": "{\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html\",\"line\":5,\"column\":1},\"groupId\":null,\"indent\":0,\"level\":\"log\",\"messageText\":null,\"parameters\":[\"mymap\",{\"type\":\"object\",\"actor\":\"server1.conn0.child1/obj36\",\"class\":\"Map\",\"extensible\":true,\"frozen\":false,\"sealed\":false,\"ownPropertyLength\":0,\"preview\":{\"kind\":\"MapLike\",\"size\":2,\"entries\":[[\"key1\",\"value1\"],[\"key2\",\"value2\"]]}}],\"source\":\"console-api\",\"type\":\"log\",\"userProvidedStyles\":[]}",
+  "repeatId": "{\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html\",\"line\":5,\"column\":1},\"groupId\":null,\"indent\":0,\"level\":\"log\",\"messageText\":null,\"parameters\":[\"mymap\",{\"type\":\"object\",\"actor\":\"server1.conn0.child1/obj37\",\"class\":\"Map\",\"extensible\":true,\"frozen\":false,\"sealed\":false,\"ownPropertyLength\":0,\"preview\":{\"kind\":\"MapLike\",\"size\":2,\"entries\":[[\"key1\",\"value1\"],[\"key2\",\"value2\"]]}}],\"source\":\"console-api\",\"type\":\"log\",\"userProvidedStyles\":[]}",
   "stacktrace": null,
   "frame": {
     "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
     "line": 5,
     "column": 1
   },
   "groupId": null,
   "exceptionDocURL": null,
   "userProvidedStyles": [],
   "notes": null,
   "indent": 0
 }));
 
+stubPreparedMessages.set("console.log('myset')", new ConsoleMessage({
+  "id": "1",
+  "allowRepeating": true,
+  "source": "console-api",
+  "timeStamp": 1501506737051,
+  "type": "log",
+  "helperType": null,
+  "level": "log",
+  "messageText": null,
+  "parameters": [
+    "myset",
+    {
+      "type": "object",
+      "actor": "server1.conn0.child1/obj38",
+      "class": "Set",
+      "extensible": true,
+      "frozen": false,
+      "sealed": false,
+      "ownPropertyLength": 0,
+      "preview": {
+        "kind": "ArrayLike",
+        "length": 2,
+        "items": [
+          "a",
+          "b"
+        ]
+      }
+    }
+  ],
+  "repeatId": "{\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html\",\"line\":2,\"column\":1},\"groupId\":null,\"indent\":0,\"level\":\"log\",\"messageText\":null,\"parameters\":[\"myset\",{\"type\":\"object\",\"actor\":\"server1.conn0.child1/obj38\",\"class\":\"Set\",\"extensible\":true,\"frozen\":false,\"sealed\":false,\"ownPropertyLength\":0,\"preview\":{\"kind\":\"ArrayLike\",\"length\":2,\"items\":[\"a\",\"b\"]}}],\"source\":\"console-api\",\"type\":\"log\",\"userProvidedStyles\":[]}",
+  "stacktrace": null,
+  "frame": {
+    "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
+    "line": 2,
+    "column": 1
+  },
+  "groupId": null,
+  "exceptionDocURL": null,
+  "userProvidedStyles": [],
+  "notes": null,
+  "indent": 0
+}));
+
 stubPreparedMessages.set("console.trace()", new ConsoleMessage({
   "id": "1",
   "allowRepeating": true,
   "source": "console-api",
   "timeStamp": 1479159910198,
   "type": "trace",
   "helperType": null,
   "level": "log",
@@ -1684,26 +1727,26 @@ stubPackets.set("console.log('myobject',
     "styles": [],
     "timeStamp": 1493125748177,
     "timer": null,
     "workerType": "none",
     "category": "webdev"
   }
 });
 
-stubPackets.set("console.map('mymap')", {
+stubPackets.set("console.log('mymap')", {
   "from": "server1.conn0.child1/consoleActor2",
   "type": "consoleAPICall",
   "message": {
     "addonId": "",
     "arguments": [
       "mymap",
       {
         "type": "object",
-        "actor": "server1.conn0.child1/obj36",
+        "actor": "server1.conn0.child1/obj37",
         "class": "Map",
         "extensible": true,
         "frozen": false,
         "sealed": false,
         "ownPropertyLength": 0,
         "preview": {
           "kind": "MapLike",
           "size": 2,
@@ -1724,17 +1767,58 @@ stubPackets.set("console.map('mymap')", 
     "counter": null,
     "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
     "functionName": "triggerPacket",
     "groupName": "",
     "level": "log",
     "lineNumber": 5,
     "private": false,
     "styles": [],
-    "timeStamp": 1493125410207,
+    "timeStamp": 1501506737042,
+    "timer": null,
+    "workerType": "none",
+    "category": "webdev"
+  }
+});
+
+stubPackets.set("console.log('myset')", {
+  "from": "server1.conn0.child1/consoleActor2",
+  "type": "consoleAPICall",
+  "message": {
+    "addonId": "",
+    "arguments": [
+      "myset",
+      {
+        "type": "object",
+        "actor": "server1.conn0.child1/obj38",
+        "class": "Set",
+        "extensible": true,
+        "frozen": false,
+        "sealed": false,
+        "ownPropertyLength": 0,
+        "preview": {
+          "kind": "ArrayLike",
+          "length": 2,
+          "items": [
+            "a",
+            "b"
+          ]
+        }
+      }
+    ],
+    "columnNumber": 1,
+    "counter": null,
+    "filename": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
+    "functionName": "triggerPacket",
+    "groupName": "",
+    "level": "log",
+    "lineNumber": 2,
+    "private": false,
+    "styles": [],
+    "timeStamp": 1501506737051,
     "timer": null,
     "workerType": "none",
     "category": "webdev"
   }
 });
 
 stubPackets.set("console.trace()", {
   "from": "server1.conn12.child1/consoleActor2",
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
@@ -38,15 +38,16 @@ skip-if = (os == 'linux' && bits == 32 &
 [browser_webconsole_input_focus.js]
 [browser_webconsole_keyboard_accessibility.js]
 [browser_webconsole_location_debugger_link.js]
 [browser_webconsole_location_scratchpad_link.js]
 [browser_webconsole_location_styleeditor_link.js]
 [browser_webconsole_network_messages_click.js]
 [browser_webconsole_nodes_highlight.js]
 [browser_webconsole_nodes_select.js]
+[browser_webconsole_object_inspector_entries.js]
 [browser_webconsole_object_inspector.js]
 [browser_webconsole_observer_notifications.js]
 [browser_webconsole_shows_reqs_in_netmonitor.js]
 [browser_webconsole_stacktrace_location_debugger_link.js]
 [browser_webconsole_stacktrace_location_scratchpad_link.js]
 [browser_webconsole_string.js]
 [browser_webconsole_timestamps.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_object_inspector_entries.js
@@ -0,0 +1,107 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Check expanding/collapsing object inspector in the console.
+const TEST_URI = "data:text/html;charset=utf8,<h1>test Object Inspector</h1>";
+
+add_task(async function () {
+  let toolbox = await openNewTabAndToolbox(TEST_URI, "webconsole");
+  let hud = toolbox.getCurrentPanel().hud;
+
+  const store = hud.ui.newConsoleOutput.getStore();
+  // Adding logging each time the store is modified in order to check
+  // the store state in case of failure.
+  store.subscribe(() => {
+    const messages = store.getState().messages.messagesById
+      .reduce(function (res, {id, type, parameters, messageText}) {
+        res.push({id, type, parameters, messageText});
+        return res;
+      }, []);
+    info("messages : " + JSON.stringify(messages));
+  });
+
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, function () {
+    content.wrappedJSObject.console.log(
+      "oi-entries-test",
+      new Map([["a", 42], ["b", 10]]),
+      new Map(
+        Array.from({length: 20})
+          .map((el, i) => [String.fromCharCode(65 + i),  i + 1])
+      ),
+    );
+  });
+
+  let node = await waitFor(() => findMessage(hud, "oi-entries-test"));
+  const objectInspectors = [...node.querySelectorAll(".tree")];
+  is(objectInspectors.length, 2, "There is the expected number of object inspectors");
+
+  const [smallMapOi, largeMapOi] = objectInspectors;
+
+  info("Expanding the small map");
+  let onMapOiMutation = waitForNodeMutation(smallMapOi, {
+    childList: true
+  });
+
+  smallMapOi.querySelector(".arrow").click();
+  await onMapOiMutation;
+
+  ok(smallMapOi.querySelector(".arrow").classList.contains("expanded"),
+    "The arrow of the node has the expected class after clicking on it");
+
+  let smallMapOiNodes = smallMapOi.querySelectorAll(".node");
+  // There are 4 nodes: the root, size, entries and the proto.
+  is(smallMapOiNodes.length, 4, "There is the expected number of nodes in the tree");
+
+  info("Expanding the <entries> leaf of the map");
+  let entriesObject = smallMapOiNodes[2];
+  is(entriesObject.textContent, "<entries>", "There is the expected <entries> node");
+  onMapOiMutation = waitForNodeMutation(smallMapOi, {
+    childList: true
+  });
+
+  entriesObject.querySelector(".arrow").click();
+  await onMapOiMutation;
+
+  ok(entriesObject.querySelector(".arrow").classList.contains("expanded"),
+    "The arrow of the node has the expected class after clicking on it");
+
+  smallMapOiNodes = smallMapOi.querySelectorAll(".node");
+  // There are now 6 nodes, the 4 original ones, and the 2 entries.
+  is(smallMapOiNodes.length, 6, "There is the expected number of nodes in the tree");
+
+  info("Expanding the large map");
+  onMapOiMutation = waitForNodeMutation(largeMapOi, {
+    childList: true
+  });
+
+  largeMapOi.querySelector(".arrow").click();
+  await onMapOiMutation;
+
+  ok(largeMapOi.querySelector(".arrow").classList.contains("expanded"),
+    "The arrow of the node has the expected class after clicking on it");
+
+  let largeMapOiNodes = largeMapOi.querySelectorAll(".node");
+  // There are 4 nodes: the root, size, entries and the proto.
+  is(largeMapOiNodes.length, 4, "There is the expected number of nodes in the tree");
+
+  info("Expanding the <entries> leaf of the map");
+  entriesObject = largeMapOiNodes[2];
+  is(entriesObject.textContent, "<entries>", "There is the expected <entries> node");
+  onMapOiMutation = waitForNodeMutation(largeMapOi, {
+    childList: true
+  });
+
+  entriesObject.querySelector(".arrow").click();
+  await onMapOiMutation;
+
+  ok(entriesObject.querySelector(".arrow").classList.contains("expanded"),
+    "The arrow of the node has the expected class after clicking on it");
+
+  largeMapOiNodes = largeMapOi.querySelectorAll(".node");
+  // There are now 24 nodes, the 4 original ones, and the 20 entries.
+  is(largeMapOiNodes.length, 24, "There is the expected number of nodes in the tree");
+});
--- a/devtools/client/webconsole/new-console-output/test/store/messages.test.js
+++ b/devtools/client/webconsole/new-console-output/test/store/messages.test.js
@@ -7,16 +7,17 @@ const {
   getAllMessagesById,
   getAllMessagesTableDataById,
   getAllMessagesUiById,
   getAllNetworkMessagesUpdateById,
   getAllRepeatById,
   getCurrentGroup,
   getVisibleMessages,
   getAllMessagesObjectPropertiesById,
+  getAllMessagesObjectEntriesById,
 } = require("devtools/client/webconsole/new-console-output/selectors/messages");
 const {
   clonePacket,
   getMessageAt,
   setupActions,
   setupStore,
 } = require("devtools/client/webconsole/new-console-output/test/helpers");
 const { stubPackets, stubPreparedMessages } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs/index");
@@ -864,9 +865,105 @@ describe("Message reducer:", () => {
       });
 
       // This addition will remove the second table message.
       dispatch(actions.messageAdd(stubPackets.get("console.log('foobar', 'test')")));
 
       expect(getAllMessagesObjectPropertiesById(getState()).size).toBe(0);
     });
   });
+
+  describe("messagesObjectEntriesById", () => {
+    it(`adds messagesObjectEntriesById data in response to
+        MESSAGE_OBJECT_ENTRIES_RECEIVE action`, () => {
+      const { dispatch, getState } = setupStore([]);
+
+      // Add 2 log messages with loaded entries.
+      dispatch(actions.messageAdd(stubPackets.get("console.log('myset')")));
+      dispatch(actions.messageAdd(stubPackets.get("console.log('mymap')")));
+
+      let messages = getAllMessagesById(getState());
+
+      const setEntries = Symbol();
+      const mapEntries = Symbol();
+      const mapEntries2 = Symbol();
+
+      const [id1, id2] = [...messages.keys()];
+      dispatch(actions.messageObjectEntriesReceive(id1, "fakeActor1", setEntries));
+      dispatch(actions.messageObjectEntriesReceive(id2, "fakeActor2", mapEntries));
+      dispatch(actions.messageObjectEntriesReceive(id2, "fakeActor3", mapEntries2));
+
+      let loadedEntries = getAllMessagesObjectEntriesById(getState());
+      expect(loadedEntries.size).toBe(2);
+
+      expect(loadedEntries.get(id1)).toEqual({
+        fakeActor1: setEntries,
+      });
+
+      expect(loadedEntries.get(id2)).toEqual({
+        fakeActor2: mapEntries,
+        fakeActor3: mapEntries2,
+      });
+    });
+
+    it("resets messagesObjectEntriesById in response to MESSAGES_CLEAR action", () => {
+      const { dispatch, getState } = setupStore([
+        "console.log('myset')"
+      ]);
+
+      let messages = getAllMessagesById(getState());
+      const entries = Symbol("entries");
+      const message = messages.first();
+      const {actor} = message.parameters[1];
+
+      dispatch(actions.messageObjectEntriesReceive(message.id, actor, entries));
+
+      let loadedEntries = getAllMessagesObjectEntriesById(getState());
+      expect(loadedEntries.size).toBe(1);
+      expect(loadedEntries.get(message.id)).toEqual({
+        [actor]: entries
+      });
+
+      dispatch(actions.messagesClear());
+
+      expect(getAllMessagesObjectEntriesById(getState()).size).toBe(0);
+    });
+
+    it("cleans messagesObjectPropertiesById when messages are pruned", () => {
+      const { dispatch, getState } = setupStore([], null, {
+        logLimit: 2
+      });
+
+      // Add 2 log messages with loaded entries.
+      dispatch(actions.messageAdd(stubPackets.get("console.log('myset')")));
+      dispatch(actions.messageAdd(stubPackets.get("console.log('mymap')")));
+
+      let messages = getAllMessagesById(getState());
+
+      const setEntries = Symbol();
+      const mapEntries = Symbol();
+      const mapEntries2 = Symbol();
+
+      const [id1, id2] = [...messages.keys()];
+      dispatch(actions.messageObjectEntriesReceive(id1, "fakeActor1", setEntries));
+      dispatch(actions.messageObjectEntriesReceive(id2, "fakeActor2", mapEntries));
+      dispatch(actions.messageObjectEntriesReceive(id2, "fakeActor3", mapEntries2));
+
+      let loadedEntries = getAllMessagesObjectEntriesById(getState());
+      expect(loadedEntries.size).toBe(2);
+
+      // This addition will remove the first message.
+      dispatch(actions.messageAdd(stubPackets.get("console.log(undefined)")));
+
+      loadedEntries = getAllMessagesObjectEntriesById(getState());
+      expect(loadedEntries.size).toBe(1);
+      expect(loadedEntries.get(id2)).toEqual({
+        fakeActor2: mapEntries,
+        fakeActor3: mapEntries2,
+      });
+
+      // This addition will remove the second table message.
+      dispatch(actions.messageAdd(stubPackets.get("console.log('foobar', 'test')")));
+
+      expect(getAllMessagesObjectEntriesById(getState()).size).toBe(0);
+    });
+  });
 });
--- a/devtools/client/webconsole/new-console-output/test/store/release-actors.test.js
+++ b/devtools/client/webconsole/new-console-output/test/store/release-actors.test.js
@@ -80,29 +80,37 @@ describe("Release actor enhancer:", () =
       dispatch(actions.messageAdd(
         stubPackets.get("console.log('myarray', ['red', 'green', 'blue'])")));
 
       let messages = getAllMessagesById(getState());
       const firstMessage = messages.first();
       const firstMessageActor = firstMessage.parameters[1].actor;
       const arrayProperties = Symbol();
       const arraySubProperties = Symbol();
+      const mapEntries1 = Symbol();
+      const mapEntries2 = Symbol();
       const [id] = [...messages.keys()];
       dispatch(actions.messageObjectPropertiesReceive(
         id, "fakeActor1", arrayProperties));
       dispatch(actions.messageObjectPropertiesReceive(
         id, "fakeActor2", arraySubProperties));
+      dispatch(actions.messageObjectEntriesReceive(
+        id, "mapActor1", mapEntries1));
+      dispatch(actions.messageObjectEntriesReceive(
+        id, "mapActor2", mapEntries2));
 
       const packet = clonePacket(stubPackets.get(
         "console.assert(false, {message: 'foobar'})"));
       const secondMessageActor = packet.message.arguments[0].actor;
       dispatch(actions.messageAdd(packet));
 
       dispatch(actions.messagesClear());
 
-      expect(releasedActors.length).toBe(4);
+      expect(releasedActors.length).toBe(6);
       expect(releasedActors).toInclude(firstMessageActor);
       expect(releasedActors).toInclude("fakeActor1");
       expect(releasedActors).toInclude("fakeActor2");
+      expect(releasedActors).toInclude("mapActor1");
+      expect(releasedActors).toInclude("mapActor2");
       expect(releasedActors).toInclude(secondMessageActor);
     });
   });
 });
--- a/devtools/client/webconsole/new-console-output/test/store/search.test.js
+++ b/devtools/client/webconsole/new-console-output/test/store/search.test.js
@@ -83,15 +83,15 @@ describe("Searching in grips", () => {
 function prepareBaseStore() {
   const store = setupStore([
     "console.log('foobar', 'test')",
     "console.warn('danger, will robinson!')",
     "console.table(['red', 'green', 'blue']);",
     "console.count('bar')",
     "console.log('myarray', ['red', 'green', 'blue'])",
     "console.log('myregex', /a.b.c/)",
-    "console.map('mymap')",
+    "console.log('mymap')",
     "console.log('myobject', {red: 'redValue', green: 'greenValue', blue: 'blueValue'});",
     "GET request",
   ]);
 
   return store;
 }