Bug 1313119 - wrap all rep render() methods with try/catch;r=nchevobbe draft
authorJulian Descottes <jdescottes@mozilla.com>
Fri, 30 Dec 2016 18:12:38 +0100
changeset 457146 1f13b2995a20fe12d3c29e9138f7bbd382fac9d4
parent 456196 f13abb8ba9f366c9f32a3146245adf642528becd
child 541400 1e053555fd3035f218660e97c1fcbb4d709fa2a7
push id40688
push userjdescottes@mozilla.com
push dateFri, 06 Jan 2017 21:43:45 +0000
reviewersnchevobbe
bugs1313119
milestone53.0a1
Bug 1313119 - wrap all rep render() methods with try/catch;r=nchevobbe MozReview-Commit-ID: 5nXcv3in4WT
devtools/client/shared/components/reps/array.js
devtools/client/shared/components/reps/attribute.js
devtools/client/shared/components/reps/caption.js
devtools/client/shared/components/reps/comment-node.js
devtools/client/shared/components/reps/date-time.js
devtools/client/shared/components/reps/document.js
devtools/client/shared/components/reps/element-node.js
devtools/client/shared/components/reps/error.js
devtools/client/shared/components/reps/event.js
devtools/client/shared/components/reps/function.js
devtools/client/shared/components/reps/grip-array.js
devtools/client/shared/components/reps/grip-map.js
devtools/client/shared/components/reps/grip.js
devtools/client/shared/components/reps/infinity.js
devtools/client/shared/components/reps/long-string.js
devtools/client/shared/components/reps/nan.js
devtools/client/shared/components/reps/null.js
devtools/client/shared/components/reps/number.js
devtools/client/shared/components/reps/object-with-text.js
devtools/client/shared/components/reps/object-with-url.js
devtools/client/shared/components/reps/object.js
devtools/client/shared/components/reps/promise.js
devtools/client/shared/components/reps/prop-rep.js
devtools/client/shared/components/reps/regexp.js
devtools/client/shared/components/reps/rep-utils.js
devtools/client/shared/components/reps/reps.css
devtools/client/shared/components/reps/string.js
devtools/client/shared/components/reps/stylesheet.js
devtools/client/shared/components/reps/symbol.js
devtools/client/shared/components/reps/text-node.js
devtools/client/shared/components/reps/undefined.js
devtools/client/shared/components/reps/window.js
devtools/client/shared/components/test/mochitest/chrome.ini
devtools/client/shared/components/test/mochitest/test_reps_failure.html
--- a/devtools/client/shared/components/reps/array.js
+++ b/devtools/client/shared/components/reps/array.js
@@ -5,17 +5,20 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // Dependencies
   const React = require("devtools/client/shared/vendor/react");
-  const { createFactories } = require("./rep-utils");
+  const {
+    createFactories,
+    wrapRender,
+  } = require("./rep-utils");
   const { Caption } = createFactories(require("./caption"));
   const { MODE } = require("./constants");
 
   // Shortcuts
   const DOM = React.DOM;
 
   /**
    * Renders an array. The array is enclosed by left and right bracket
@@ -111,17 +114,17 @@ define(function (require, exports, modul
     // Event Handlers
 
     onToggleProperties: function (event) {
     },
 
     onClickBracket: function (event) {
     },
 
-    render: function () {
+    render: wrapRender(function () {
       let {
         object,
         mode = MODE.SHORT,
       } = this.props;
 
       let items;
       let brackets;
       let needSpace = function (space) {
@@ -153,38 +156,38 @@ define(function (require, exports, modul
             object: object
           }, brackets.right),
           DOM.span({
             className: "arrayProperties",
             role: "group"}
           )
         )
       );
-    },
+    }),
   });
 
   /**
    * Renders array item. Individual values are separated by a comma.
    */
   let ItemRep = React.createFactory(React.createClass({
     displayName: "ItemRep",
 
-    render: function () {
+    render: wrapRender(function () {
       const { Rep } = createFactories(require("./rep"));
 
       let object = this.props.object;
       let delim = this.props.delim;
       let mode = this.props.mode;
       return (
         DOM.span({},
           Rep({object: object, mode: mode}),
           delim
         )
       );
-    }
+    })
   }));
 
   function supportsObject(object, type) {
     return Array.isArray(object) ||
       Object.prototype.toString.call(object) === "[object Arguments]";
   }
 
   // Exports from this module
--- a/devtools/client/shared/components/reps/attribute.js
+++ b/devtools/client/shared/components/reps/attribute.js
@@ -6,17 +6,21 @@
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
 
   // Reps
-  const { createFactories, isGrip } = require("./rep-utils");
+  const {
+    createFactories,
+    isGrip,
+    wrapRender,
+  } = require("./rep-utils");
   const { StringRep } = require("./string");
 
   // Shortcuts
   const { span } = React.DOM;
   const { rep: StringRepFactory } = createFactories(StringRep);
 
   /**
    * Renders DOM attribute
@@ -27,17 +31,17 @@ define(function (require, exports, modul
     propTypes: {
       object: React.PropTypes.object.isRequired
     },
 
     getTitle: function (grip) {
       return grip.preview.nodeName;
     },
 
-    render: function () {
+    render: wrapRender(function () {
       let object = this.props.object;
       let value = object.preview.value;
       let objectLink = this.props.objectLink || span;
 
       return (
         objectLink({className: "objectLink-Attr", object},
           span({},
             span({className: "attrTitle"},
@@ -45,17 +49,17 @@ define(function (require, exports, modul
             ),
             span({className: "attrEqual"},
               "="
             ),
             StringRepFactory({object: value})
           )
         )
       );
-    },
+    }),
   });
 
   // Registration
 
   function supportsObject(grip, type) {
     if (!isGrip(grip)) {
       return false;
     }
--- a/devtools/client/shared/components/reps/caption.js
+++ b/devtools/client/shared/components/reps/caption.js
@@ -7,25 +7,27 @@
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // Dependencies
   const React = require("devtools/client/shared/vendor/react");
   const DOM = React.DOM;
 
+  const { wrapRender } = require("./rep-utils");
+
   /**
    * Renders a caption. This template is used by other components
    * that needs to distinguish between a simple text/value and a label.
    */
   const Caption = React.createClass({
     displayName: "Caption",
 
-    render: function () {
+    render: wrapRender(function () {
       return (
         DOM.span({"className": "caption"}, this.props.object)
       );
-    },
+    }),
   });
 
   // Exports from this module
   exports.Caption = Caption;
 });
--- a/devtools/client/shared/components/reps/comment-node.js
+++ b/devtools/client/shared/components/reps/comment-node.js
@@ -4,17 +4,22 @@
  * 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/. */
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // Dependencies
   const React = require("devtools/client/shared/vendor/react");
-  const { isGrip, cropString, cropMultipleLines } = require("./rep-utils");
+  const {
+    isGrip,
+    cropString,
+    cropMultipleLines,
+    wrapRender,
+  } = require("./rep-utils");
   const { MODE } = require("./constants");
   const nodeConstants = require("devtools/shared/dom-node-constants");
 
   // Shortcuts
   const { span } = React.DOM;
 
   /**
    * Renders DOM comment node.
@@ -23,31 +28,31 @@ define(function (require, exports, modul
     displayName: "CommentNode",
 
     propTypes: {
       object: React.PropTypes.object.isRequired,
       // @TODO Change this to Object.values once it's supported in Node's version of V8
       mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
     },
 
-    render: function () {
+    render: wrapRender(function () {
       let {
         object,
         mode = MODE.SHORT
       } = this.props;
 
       let {textContent} = object.preview;
       if (mode === MODE.TINY) {
         textContent = cropMultipleLines(textContent, 30);
       } else if (mode === MODE.SHORT) {
         textContent = cropString(textContent, 50);
       }
 
       return span({className: "objectBox theme-comment"}, `<!-- ${textContent} -->`);
-    },
+    }),
   });
 
   // Registration
   function supportsObject(object, type) {
     if (!isGrip(object)) {
       return false;
     }
     return object.preview && object.preview.nodeType === nodeConstants.COMMENT_NODE;
--- a/devtools/client/shared/components/reps/date-time.js
+++ b/devtools/client/shared/components/reps/date-time.js
@@ -6,17 +6,20 @@
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
 
   // Reps
-  const { isGrip } = require("./rep-utils");
+  const {
+    isGrip,
+    wrapRender,
+  } = require("./rep-utils");
 
   // Shortcuts
   const { span } = React.DOM;
 
   /**
    * Used to render JS built-in Date() object.
    */
   let DateTime = React.createClass({
@@ -30,31 +33,32 @@ define(function (require, exports, modul
       if (this.props.objectLink) {
         return this.props.objectLink({
           object: grip
         }, grip.class + " ");
       }
       return "";
     },
 
-    render: function () {
+    render: wrapRender(function () {
       let grip = this.props.object;
       let date;
       try {
         date = span({className: "objectBox"},
           this.getTitle(grip),
           span({className: "Date"},
             new Date(grip.preview.timestamp).toISOString()
           )
         );
       } catch (e) {
         date = span({className: "objectBox"}, "Invalid Date");
       }
+
       return date;
-    },
+    }),
   });
 
   // Registration
 
   function supportsObject(grip, type) {
     if (!isGrip(grip)) {
       return false;
     }
--- a/devtools/client/shared/components/reps/document.js
+++ b/devtools/client/shared/components/reps/document.js
@@ -6,17 +6,21 @@
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
 
   // Reps
-  const { isGrip, getURLDisplayString } = require("./rep-utils");
+  const {
+    isGrip,
+    getURLDisplayString,
+    wrapRender,
+  } = require("./rep-utils");
 
   // Shortcuts
   const { span } = React.DOM;
 
   /**
    * Renders DOM document object.
    */
   let Document = React.createClass({
@@ -41,28 +45,28 @@ define(function (require, exports, modul
       }
       return "";
     },
 
     getTooltip: function (doc) {
       return doc.location.href;
     },
 
-    render: function () {
+    render: wrapRender(function () {
       let grip = this.props.object;
 
       return (
         span({className: "objectBox objectBox-object"},
           this.getTitle(grip),
           span({className: "objectPropValue"},
             this.getLocation(grip)
           )
         )
       );
-    },
+    }),
   });
 
   // Registration
 
   function supportsObject(object, type) {
     if (!isGrip(object)) {
       return false;
     }
--- a/devtools/client/shared/components/reps/element-node.js
+++ b/devtools/client/shared/components/reps/element-node.js
@@ -6,17 +6,20 @@
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
 
   // Utils
-  const { isGrip } = require("./rep-utils");
+  const {
+    isGrip,
+    wrapRender,
+  } = require("./rep-utils");
   const { MODE } = require("./constants");
   const nodeConstants = require("devtools/shared/dom-node-constants");
 
   // Shortcuts
   const { span } = React.DOM;
 
   /**
    * Renders DOM element node.
@@ -83,17 +86,17 @@ define(function (require, exports, modul
       return [
         "<",
         nodeNameElement,
         ...attributeElements,
         ">",
       ];
     },
 
-    render: function () {
+    render: wrapRender(function () {
       let {
         object,
         mode,
         onDOMNodeMouseOver,
         onDOMNodeMouseOut
       } = this.props;
       let elements = this.getElements(object, mode);
       let objectLink = this.props.objectLink || span;
@@ -109,17 +112,17 @@ define(function (require, exports, modul
         Object.assign(baseConfig, {
           onMouseOut: onDOMNodeMouseOut
         });
       }
 
       return objectLink({object},
         span(baseConfig, ...elements)
       );
-    },
+    }),
   });
 
   // Registration
   function supportsObject(object, type) {
     if (!isGrip(object)) {
       return false;
     }
     return object.preview && object.preview.nodeType === nodeConstants.ELEMENT_NODE;
--- a/devtools/client/shared/components/reps/error.js
+++ b/devtools/client/shared/components/reps/error.js
@@ -3,34 +3,37 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
   // Utils
-  const { isGrip } = require("./rep-utils");
+  const {
+    isGrip,
+    wrapRender,
+  } = require("./rep-utils");
   const { MODE } = require("./constants");
   // Shortcuts
   const { span } = React.DOM;
 
   /**
    * Renders Error objects.
    */
   const ErrorRep = React.createClass({
     displayName: "Error",
 
     propTypes: {
       object: React.PropTypes.object.isRequired,
       // @TODO Change this to Object.values once it's supported in Node's version of V8
       mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
     },
 
-    render: function () {
+    render: wrapRender(function () {
       let object = this.props.object;
       let preview = object.preview;
       let name = preview && preview.name
         ? preview.name
         : "Error";
 
       let content = this.props.mode === MODE.TINY
         ? name
@@ -46,17 +49,17 @@ define(function (require, exports, modul
       }
 
       let objectLink = this.props.objectLink || span;
       return (
         objectLink({object, className: "objectBox-stackTrace"},
           span({}, content)
         )
       );
-    },
+    }),
   });
 
   // Registration
   function supportsObject(object, type) {
     if (!isGrip(object)) {
       return false;
     }
     return (object.preview && type === "Error");
--- a/devtools/client/shared/components/reps/event.js
+++ b/devtools/client/shared/components/reps/event.js
@@ -6,17 +6,21 @@
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
 
   // Reps
-  const { createFactories, isGrip } = require("./rep-utils");
+  const {
+    createFactories,
+    isGrip,
+    wrapRender,
+  } = require("./rep-utils");
   const { rep } = createFactories(require("./grip").Grip);
 
   /**
    * Renders DOM event objects.
    */
   let Event = React.createClass({
     displayName: "event",
 
@@ -29,17 +33,17 @@ define(function (require, exports, modul
       let title = preview.type;
 
       if (preview.eventKind == "key" && preview.modifiers && preview.modifiers.length) {
         title = `${title} ${preview.modifiers.join("-")}`;
       }
       return title;
     },
 
-    render: function () {
+    render: wrapRender(function () {
       // Use `Object.assign` to keep `this.props` without changes because:
       // 1. JSON.stringify/JSON.parse is slow.
       // 2. Immutable.js is planned for the future.
       let props = Object.assign({
         title: this.getTitle(this.props)
       }, this.props);
       props.object = Object.assign({}, this.props.object);
       props.object.preview = Object.assign({}, this.props.object.preview);
@@ -75,17 +79,17 @@ define(function (require, exports, modul
         default:
           props.isInterestingProp = (type, value, name) => {
             // We want to show the properties in the order they are declared.
             return Object.keys(props.object.preview.ownProperties).includes(name);
           };
       }
 
       return rep(props);
-    }
+    })
   });
 
   // Registration
 
   function supportsObject(grip, type) {
     if (!isGrip(grip)) {
       return false;
     }
--- a/devtools/client/shared/components/reps/function.js
+++ b/devtools/client/shared/components/reps/function.js
@@ -6,17 +6,21 @@
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
 
   // Reps
-  const { isGrip, cropString } = require("./rep-utils");
+  const {
+    isGrip,
+    cropString,
+    wrapRender,
+  } = require("./rep-utils");
 
   // Shortcuts
   const { span } = React.DOM;
 
   /**
    * This component represents a template for Function objects.
    */
   let Func = React.createClass({
@@ -35,28 +39,28 @@ define(function (require, exports, modul
       return "";
     },
 
     summarizeFunction: function (grip) {
       let name = grip.userDisplayName || grip.displayName || grip.name || "function";
       return cropString(name + "()", 100);
     },
 
-    render: function () {
+    render: wrapRender(function () {
       let grip = this.props.object;
 
       return (
         // Set dir="ltr" to prevent function parentheses from
         // appearing in the wrong direction
         span({dir: "ltr", className: "objectBox objectBox-function"},
           this.getTitle(grip),
           this.summarizeFunction(grip)
         )
       );
-    },
+    }),
   });
 
   // Registration
 
   function supportsObject(grip, type) {
     if (!isGrip(grip)) {
       return (type == "function");
     }
--- a/devtools/client/shared/components/reps/grip-array.js
+++ b/devtools/client/shared/components/reps/grip-array.js
@@ -5,17 +5,21 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // Dependencies
   const React = require("devtools/client/shared/vendor/react");
-  const { createFactories, isGrip } = require("./rep-utils");
+  const {
+    createFactories,
+    isGrip,
+    wrapRender,
+  } = require("./rep-utils");
   const { Caption } = createFactories(require("./caption"));
   const { MODE } = require("./constants");
 
   // Shortcuts
   const { span } = React.DOM;
 
   /**
    * Renders an array. The array is enclosed by left and right bracket
@@ -104,17 +108,17 @@ define(function (require, exports, modul
             object: this.props.object
           }, leftItemNum + " more…")
         }));
       }
 
       return items;
     },
 
-    render: function () {
+    render: wrapRender(function () {
       let {
         object,
         mode = MODE.SHORT
       } = this.props;
 
       let items;
       let brackets;
       let needSpace = function (space) {
@@ -149,17 +153,17 @@ define(function (require, exports, modul
             object: object
           }, brackets.right),
           span({
             className: "arrayProperties",
             role: "group"}
           )
         )
       );
-    },
+    }),
   });
 
   /**
    * Renders array item. Individual values are separated by
    * a delimiter (a comma by default).
    */
   let GripArrayItem = React.createFactory(React.createClass({
     displayName: "GripArrayItem",
--- a/devtools/client/shared/components/reps/grip-map.js
+++ b/devtools/client/shared/components/reps/grip-map.js
@@ -4,17 +4,21 @@
  * 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/. */
 
 "use strict";
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // Dependencies
   const React = require("devtools/client/shared/vendor/react");
-  const { createFactories, isGrip } = require("./rep-utils");
+  const {
+    createFactories,
+    isGrip,
+    wrapRender,
+  } = require("./rep-utils");
   const { Caption } = createFactories(require("./caption"));
   const { PropRep } = createFactories(require("./prop-rep"));
   const { MODE } = require("./constants");
   // Shortcuts
   const { span } = React.DOM;
   /**
    * Renders an map. A map is represented by a list of its
    * entries enclosed in curly brackets.
@@ -139,17 +143,17 @@ define(function (require, exports, modul
               indexes.push(i);
             }
           }
 
           return indexes;
         }, []);
     },
 
-    render: function () {
+    render: wrapRender(function () {
       let object = this.props.object;
       let props = this.safeEntriesIterator(object,
         (this.props.mode === MODE.LONG) ? 10 : 3);
 
       let objectLink = this.props.objectLink || span;
       if (this.props.mode === MODE.TINY) {
         return (
           span({className: "objectBox objectBox-object"},
@@ -171,17 +175,17 @@ define(function (require, exports, modul
           }, " { "),
           props,
           objectLink({
             className: "objectRightBrace",
             object: object
           }, " }")
         )
       );
-    },
+    }),
   });
 
   function supportsObject(grip, type) {
     if (!isGrip(grip)) {
       return false;
     }
     return (grip.preview && grip.preview.kind == "MapLike");
   }
--- a/devtools/client/shared/components/reps/grip.js
+++ b/devtools/client/shared/components/reps/grip.js
@@ -5,17 +5,21 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
   // Dependencies
-  const { createFactories, isGrip } = require("./rep-utils");
+  const {
+    createFactories,
+    isGrip,
+    wrapRender,
+  } = require("./rep-utils");
   const { Caption } = createFactories(require("./caption"));
   const { PropRep } = createFactories(require("./prop-rep"));
   const { MODE } = require("./constants");
   // Shortcuts
   const { span } = React.DOM;
 
   /**
    * Renders generic grip. Grip is client representation
@@ -193,17 +197,17 @@ define(function (require, exports, modul
           value = property.value;
         } else if (keys.includes("getterValue")) {
           value = property.getterValue;
         }
       }
       return value;
     },
 
-    render: function () {
+    render: wrapRender(function () {
       let object = this.props.object;
       let props = this.safePropIterator(object,
         (this.props.mode === MODE.LONG) ? 10 : 3);
 
       let objectLink = this.props.objectLink || span;
       if (this.props.mode === MODE.TINY) {
         return (
           span({className: "objectBox objectBox-object"},
@@ -225,17 +229,17 @@ define(function (require, exports, modul
           }, " { "),
           ...props,
           objectLink({
             className: "objectRightBrace",
             object: object
           }, " }")
         )
       );
-    },
+    }),
   });
 
   // Registration
   function supportsObject(object, type) {
     if (!isGrip(object)) {
       return false;
     }
     return (object.preview && object.preview.ownProperties);
--- a/devtools/client/shared/components/reps/infinity.js
+++ b/devtools/client/shared/components/reps/infinity.js
@@ -6,32 +6,34 @@
 
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // Dependencies
   const React = require("devtools/client/shared/vendor/react");
 
+  const { wrapRender } = require("./rep-utils");
+
   // Shortcuts
   const { span } = React.DOM;
 
   /**
    * Renders a Infinity object
    */
   const InfinityRep = React.createClass({
     displayName: "Infinity",
 
-    render: function () {
+    render: wrapRender(function () {
       return (
         span({className: "objectBox objectBox-number"},
           this.props.object.type
         )
       );
-    }
+    })
   });
 
   function supportsObject(object, type) {
     return type == "Infinity" || type == "-Infinity";
   }
 
   // Exports from this module
   exports.InfinityRep = {
--- a/devtools/client/shared/components/reps/long-string.js
+++ b/devtools/client/shared/components/reps/long-string.js
@@ -3,17 +3,21 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // Dependencies
   const React = require("devtools/client/shared/vendor/react");
-  const { sanitizeString, isGrip } = require("./rep-utils");
+  const {
+    sanitizeString,
+    isGrip,
+    wrapRender,
+  } = require("./rep-utils");
   // Shortcuts
   const { span } = React.DOM;
 
   /**
    * Renders a long string grip.
    */
   const LongStringRep = React.createClass({
     displayName: "LongStringRep",
@@ -24,17 +28,17 @@ define(function (require, exports, modul
     },
 
     getDefaultProps: function () {
       return {
         useQuotes: true,
       };
     },
 
-    render: function () {
+    render: wrapRender(function () {
       let {
         cropLimit,
         member,
         object,
         style,
         useQuotes
       } = this.props;
       let {fullText, initial, length} = object;
@@ -48,17 +52,17 @@ define(function (require, exports, modul
         ? fullText || initial
         : initial.substring(0, cropLimit);
 
       if (string.length < length) {
         string += "\u2026";
       }
       let formattedString = useQuotes ? `"${string}"` : string;
       return span(config, sanitizeString(formattedString));
-    },
+    }),
   });
 
   function supportsObject(object, type) {
     if (!isGrip(object)) {
       return false;
     }
     return object.type === "longString";
   }
--- a/devtools/client/shared/components/reps/nan.js
+++ b/devtools/client/shared/components/reps/nan.js
@@ -6,32 +6,34 @@
 
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // Dependencies
   const React = require("devtools/client/shared/vendor/react");
 
+  const { wrapRender } = require("./rep-utils");
+
   // Shortcuts
   const { span } = React.DOM;
 
   /**
    * Renders a NaN object
    */
   const NaNRep = React.createClass({
     displayName: "NaN",
 
-    render: function () {
+    render: wrapRender(function () {
       return (
         span({className: "objectBox objectBox-nan"},
           "NaN"
         )
       );
-    }
+    })
   });
 
   function supportsObject(object, type) {
     return type == "NaN";
   }
 
   // Exports from this module
   exports.NaNRep = {
--- a/devtools/client/shared/components/reps/null.js
+++ b/devtools/client/shared/components/reps/null.js
@@ -6,32 +6,34 @@
 
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // Dependencies
   const React = require("devtools/client/shared/vendor/react");
 
+  const { wrapRender } = require("./rep-utils");
+
   // Shortcuts
   const { span } = React.DOM;
 
   /**
    * Renders null value
    */
   const Null = React.createClass({
     displayName: "NullRep",
 
-    render: function () {
+    render: wrapRender(function () {
       return (
         span({className: "objectBox objectBox-null"},
           "null"
         )
       );
-    },
+    }),
   });
 
   function supportsObject(object, type) {
     if (object && object.type && object.type == "null") {
       return true;
     }
 
     return (object == null);
--- a/devtools/client/shared/components/reps/number.js
+++ b/devtools/client/shared/components/reps/number.js
@@ -6,41 +6,43 @@
 
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // Dependencies
   const React = require("devtools/client/shared/vendor/react");
 
+  const { wrapRender } = require("./rep-utils");
+
   // Shortcuts
   const { span } = React.DOM;
 
   /**
    * Renders a number
    */
   const Number = React.createClass({
     displayName: "Number",
 
     stringify: function (object) {
       let isNegativeZero = Object.is(object, -0) ||
         (object.type && object.type == "-0");
 
       return (isNegativeZero ? "-0" : String(object));
     },
 
-    render: function () {
+    render: wrapRender(function () {
       let value = this.props.object;
 
       return (
         span({className: "objectBox objectBox-number"},
           this.stringify(value)
         )
       );
-    }
+    })
   });
 
   function supportsObject(object, type) {
     return ["boolean", "number", "-0"].includes(type);
   }
 
   // Exports from this module
 
--- a/devtools/client/shared/components/reps/object-with-text.js
+++ b/devtools/client/shared/components/reps/object-with-text.js
@@ -6,17 +6,20 @@
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
 
   // Reps
-  const { isGrip } = require("./rep-utils");
+  const {
+    isGrip,
+    wrapRender,
+  } = require("./rep-utils");
 
   // Shortcuts
   const { span } = React.DOM;
 
   /**
    * Renders a grip object with textual data.
    */
   let ObjectWithText = React.createClass({
@@ -40,27 +43,27 @@ define(function (require, exports, modul
     getType: function (grip) {
       return grip.class;
     },
 
     getDescription: function (grip) {
       return "\"" + grip.preview.text + "\"";
     },
 
-    render: function () {
+    render: wrapRender(function () {
       let grip = this.props.object;
       return (
         span({className: "objectBox objectBox-" + this.getType(grip)},
           this.getTitle(grip),
           span({className: "objectPropValue"},
             this.getDescription(grip)
           )
         )
       );
-    },
+    }),
   });
 
   // Registration
 
   function supportsObject(grip, type) {
     if (!isGrip(grip)) {
       return false;
     }
--- a/devtools/client/shared/components/reps/object-with-url.js
+++ b/devtools/client/shared/components/reps/object-with-url.js
@@ -6,17 +6,21 @@
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
 
   // Reps
-  const { isGrip, getURLDisplayString } = require("./rep-utils");
+  const {
+    isGrip,
+    getURLDisplayString,
+    wrapRender,
+  } = require("./rep-utils");
 
   // Shortcuts
   const { span } = React.DOM;
 
   /**
    * Renders a grip object with URL data.
    */
   let ObjectWithURL = React.createClass({
@@ -40,27 +44,27 @@ define(function (require, exports, modul
     getType: function (grip) {
       return grip.class;
     },
 
     getDescription: function (grip) {
       return getURLDisplayString(grip.preview.url);
     },
 
-    render: function () {
+    render: wrapRender(function () {
       let grip = this.props.object;
       return (
         span({className: "objectBox objectBox-" + this.getType(grip)},
           this.getTitle(grip),
           span({className: "objectPropValue"},
             this.getDescription(grip)
           )
         )
       );
-    },
+    }),
   });
 
   // Registration
 
   function supportsObject(grip, type) {
     if (!isGrip(grip)) {
       return false;
     }
--- a/devtools/client/shared/components/reps/object.js
+++ b/devtools/client/shared/components/reps/object.js
@@ -4,17 +4,20 @@
  * 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/. */
 
 "use strict";
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // Dependencies
   const React = require("devtools/client/shared/vendor/react");
-  const { createFactories } = require("./rep-utils");
+  const {
+    createFactories,
+    wrapRender,
+  } = require("./rep-utils");
   const { Caption } = createFactories(require("./caption"));
   const { PropRep } = createFactories(require("./prop-rep"));
   const { MODE } = require("./constants");
   // Shortcuts
   const { span } = React.DOM;
   /**
    * Renders an object. An object is represented by a list of its
    * properties enclosed in curly brackets.
@@ -127,17 +130,17 @@ define(function (require, exports, modul
         }
       } catch (err) {
         console.error(err);
       }
 
       return props;
     },
 
-    render: function () {
+    render: wrapRender(function () {
       let object = this.props.object;
       let props = this.safePropIterator(object);
       let objectLink = this.props.objectLink || span;
 
       if (this.props.mode === MODE.TINY || !props.length) {
         return (
           span({className: "objectBox objectBox-object"},
             objectLink({className: "objectTitle"}, this.getTitle(object))
@@ -154,17 +157,17 @@ define(function (require, exports, modul
           }, " { "),
           ...props,
           objectLink({
             className: "objectRightBrace",
             object: object
           }, " }")
         )
       );
-    },
+    }),
   });
   function supportsObject(object, type) {
     return true;
   }
 
   // Exports from this module
   exports.Obj = {
     rep: Obj,
--- a/devtools/client/shared/components/reps/promise.js
+++ b/devtools/client/shared/components/reps/promise.js
@@ -5,17 +5,22 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
   // Dependencies
-  const { createFactories, isGrip } = require("./rep-utils");
+  const {
+    createFactories,
+    isGrip,
+    wrapRender,
+  } = require("./rep-utils");
+
   const { PropRep } = createFactories(require("./prop-rep"));
   const { MODE } = require("./constants");
   // Shortcuts
   const { span } = React.DOM;
 
   /**
    * Renders a DOM Promise object.
    */
@@ -50,17 +55,17 @@ define(function (require, exports, modul
           name: `<${key}>`,
           object: promiseState[key],
           equal: ": ",
           delim: i < keys.length - 1 ? ", " : ""
         }));
       });
     },
 
-    render: function () {
+    render: wrapRender(function () {
       const object = this.props.object;
       const {promiseState} = object;
       let objectLink = this.props.objectLink || span;
 
       if (this.props.mode === MODE.TINY) {
         let { Rep } = createFactories(require("./rep"));
 
         return (
@@ -89,17 +94,17 @@ define(function (require, exports, modul
           }, " { "),
           ...props,
           objectLink({
             className: "objectRightBrace",
             object: object
           }, " }")
         )
       );
-    },
+    }),
   });
 
   // Registration
   function supportsObject(object, type) {
     if (!isGrip(object)) {
       return false;
     }
     return type === "Promise";
--- a/devtools/client/shared/components/reps/prop-rep.js
+++ b/devtools/client/shared/components/reps/prop-rep.js
@@ -4,44 +4,47 @@
  * 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/. */
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // Dependencies
   const React = require("devtools/client/shared/vendor/react");
-  const { createFactories } = require("./rep-utils");
+  const {
+    createFactories,
+    wrapRender,
+  } = require("./rep-utils");
   const { MODE } = require("./constants");
   // Shortcuts
   const { span } = React.DOM;
 
   /**
    * Property for Obj (local JS objects), Grip (remote JS objects)
    * and GripMap (remote JS maps and weakmaps) reps.
    * It's used to render object properties.
    */
-  let PropRep = React.createFactory(React.createClass({
+  let PropRep = React.createClass({
     displayName: "PropRep",
 
     propTypes: {
       // Property name.
       name: React.PropTypes.oneOfType([
         React.PropTypes.string,
         React.PropTypes.object,
       ]).isRequired,
       // Equal character rendered between property name and value.
       equal: React.PropTypes.string,
       // Delimiter character used to separate individual properties.
       delim: React.PropTypes.string,
       // @TODO Change this to Object.values once it's supported in Node's version of V8
       mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
     },
 
-    render: function () {
+    render: wrapRender(function () {
       const { Grip } = require("./grip");
       let { Rep } = createFactories(require("./rep"));
 
       let key;
       // The key can be a simple string, for plain objects,
       // or another object for maps and weakmaps.
       if (typeof this.props.name === "string") {
         key = span({"className": "nodeName"}, this.props.name);
@@ -61,14 +64,14 @@ define(function (require, exports, modul
             "className": "objectEqual"
           }, this.props.equal),
           Rep(this.props),
           span({
             "className": "objectComma"
           }, this.props.delim)
         )
       );
-    }
-  }));
+    })
+  });
 
   // Exports from this module
   exports.PropRep = PropRep;
 });
--- a/devtools/client/shared/components/reps/regexp.js
+++ b/devtools/client/shared/components/reps/regexp.js
@@ -6,17 +6,20 @@
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
 
   // Reps
-  const { isGrip } = require("./rep-utils");
+  const {
+    isGrip,
+    wrapRender,
+  } = require("./rep-utils");
 
   // Shortcuts
   const { span } = React.DOM;
 
   /**
    * Renders a grip object with regular expression.
    */
   let RegExp = React.createClass({
@@ -25,29 +28,29 @@ define(function (require, exports, modul
     propTypes: {
       object: React.PropTypes.object.isRequired,
     },
 
     getSource: function (grip) {
       return grip.displayString;
     },
 
-    render: function () {
+    render: wrapRender(function () {
       let grip = this.props.object;
       let objectLink = this.props.objectLink || span;
 
       return (
         span({className: "objectBox objectBox-regexp"},
           objectLink({
             object: grip,
             className: "regexpSource"
           }, this.getSource(grip))
         )
       );
-    },
+    }),
   });
 
   // Registration
 
   function supportsObject(object, type) {
     if (!isGrip(object)) {
       return false;
     }
--- a/devtools/client/shared/components/reps/rep-utils.js
+++ b/devtools/client/shared/components/reps/rep-utils.js
@@ -142,19 +142,41 @@ define(function (require, exports, modul
     return {
       protocol: m[1],
       domain: m[2],
       path: m[2] + m[3],
       name: m[4] + m[5]
     };
   }
 
+  /**
+   * Wrap the provided render() method of a rep in a try/catch block that will render a
+   * fallback rep if the render fails.
+   */
+  function wrapRender(renderMethod) {
+    return function () {
+      try {
+        return renderMethod.call(this);
+      } catch (e) {
+        return React.DOM.span(
+          {
+            className: "objectBox objectBox-failure",
+            title: "This object could not be rendered, " +
+                   "please file a bug on bugzilla.mozilla.org"
+          },
+          /* Labels have to be hardcoded for reps, see Bug 1317038. */
+          "Invalid object");
+      }
+    };
+  }
+
   // Exports from this module
   exports.createFactories = createFactories;
   exports.isGrip = isGrip;
   exports.cropString = cropString;
   exports.cropMultipleLines = cropMultipleLines;
   exports.parseURLParams = parseURLParams;
   exports.parseURLEncodedText = parseURLEncodedText;
   exports.getFileName = getFileName;
   exports.getURLDisplayString = getURLDisplayString;
+  exports.wrapRender = wrapRender;
   exports.sanitizeString = sanitizeString;
 });
--- a/devtools/client/shared/components/reps/reps.css
+++ b/devtools/client/shared/components/reps/reps.css
@@ -91,16 +91,25 @@
   position: absolute;
   right: 4px;
   top: 2px;
   padding-left: 8px;
   font-weight: bold;
   color: var(--source-link-color);
 }
 
+.objectBox-failure {
+  color: var(--string-color);
+  border-width: 1px;
+  border-style: solid;
+  border-radius: 2px;
+  font-size: 0.8em;
+  padding: 0 2px;
+}
+
 /******************************************************************************/
 
 .objectLink-event,
 .objectLink-eventLog,
 .objectLink-regexp,
 .objectLink-object,
 .objectLink-Date {
   font-weight: bold;
--- a/devtools/client/shared/components/reps/string.js
+++ b/devtools/client/shared/components/reps/string.js
@@ -5,17 +5,21 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // Dependencies
   const React = require("devtools/client/shared/vendor/react");
-  const { cropString } = require("./rep-utils");
+
+  const {
+    cropString,
+    wrapRender,
+  } = require("./rep-utils");
 
   // Shortcuts
   const { span } = React.DOM;
 
   /**
    * Renders a string. String value is enclosed within quotes.
    */
   const StringRep = React.createClass({
@@ -27,17 +31,17 @@ define(function (require, exports, modul
     },
 
     getDefaultProps: function () {
       return {
         useQuotes: true,
       };
     },
 
-    render: function () {
+    render: wrapRender(function () {
       let text = this.props.object;
       let member = this.props.member;
       let style = this.props.style;
 
       let config = {className: "objectBox objectBox-string"};
       if (style) {
         config.style = style;
       }
@@ -48,17 +52,17 @@ define(function (require, exports, modul
 
       let croppedString = this.props.cropLimit ?
         cropString(text, this.props.cropLimit) : cropString(text);
 
       let formattedString = this.props.useQuotes ?
         "\"" + croppedString + "\"" : croppedString;
 
       return span(config, formattedString);
-    },
+    }),
   });
 
   function supportsObject(object, type) {
     return (type == "string");
   }
 
   // Exports from this module
 
--- a/devtools/client/shared/components/reps/stylesheet.js
+++ b/devtools/client/shared/components/reps/stylesheet.js
@@ -6,17 +6,21 @@
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
 
   // Reps
-  const { isGrip, getURLDisplayString } = require("./rep-utils");
+  const {
+    isGrip,
+    getURLDisplayString,
+    wrapRender
+  } = require("./rep-utils");
 
   // Shortcuts
   const DOM = React.DOM;
 
   /**
    * Renders a grip representing CSSStyleSheet
    */
   let StyleSheet = React.createClass({
@@ -39,28 +43,28 @@ define(function (require, exports, modul
     },
 
     getLocation: function (grip) {
       // Embedded stylesheets don't have URL and so, no preview.
       let url = grip.preview ? grip.preview.url : "";
       return url ? getURLDisplayString(url) : "";
     },
 
-    render: function () {
+    render: wrapRender(function () {
       let grip = this.props.object;
 
       return (
         DOM.span({className: "objectBox objectBox-object"},
           this.getTitle(grip),
           DOM.span({className: "objectPropValue"},
             this.getLocation(grip)
           )
         )
       );
-    },
+    }),
   });
 
   // Registration
 
   function supportsObject(object, type) {
     if (!isGrip(object)) {
       return false;
     }
--- a/devtools/client/shared/components/reps/symbol.js
+++ b/devtools/client/shared/components/reps/symbol.js
@@ -6,39 +6,41 @@
 
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // Dependencies
   const React = require("devtools/client/shared/vendor/react");
 
+  const { wrapRender } = require("./rep-utils");
+
   // Shortcuts
   const { span } = React.DOM;
 
   /**
    * Renders a symbol.
    */
   const SymbolRep = React.createClass({
     displayName: "SymbolRep",
 
     propTypes: {
       object: React.PropTypes.object.isRequired
     },
 
-    render: function () {
+    render: wrapRender(function () {
       let {object} = this.props;
       let {name} = object;
 
       return (
         span({className: "objectBox objectBox-symbol"},
           `Symbol(${name || ""})`
         )
       );
-    },
+    }),
   });
 
   function supportsObject(object, type) {
     return (type == "symbol");
   }
 
   // Exports from this module
   exports.SymbolRep = {
--- a/devtools/client/shared/components/reps/text-node.js
+++ b/devtools/client/shared/components/reps/text-node.js
@@ -6,17 +6,21 @@
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
 
   // Reps
-  const { isGrip, cropString } = require("./rep-utils");
+  const {
+    isGrip,
+    cropString,
+    wrapRender,
+  } = require("./rep-utils");
   const { MODE } = require("./constants");
 
   // Shortcuts
   const DOM = React.DOM;
 
   /**
    * Renders DOM #text node.
    */
@@ -38,17 +42,17 @@ define(function (require, exports, modul
       if (this.props.objectLink) {
         return this.props.objectLink({
           object: grip
         }, title);
       }
       return title;
     },
 
-    render: function () {
+    render: wrapRender(function () {
       let {
         object: grip,
         mode = MODE.SHORT,
       } = this.props;
 
       let baseConfig = {className: "objectBox objectBox-textNode"};
       if (this.props.onDOMNodeMouseOver) {
         Object.assign(baseConfig, {
@@ -70,17 +74,17 @@ define(function (require, exports, modul
         DOM.span(baseConfig,
           this.getTitle(grip),
           DOM.span({className: "nodeValue"},
             " ",
             `"${this.getTextContent(grip)}"`
           )
         )
       );
-    },
+    }),
   });
 
   // Registration
 
   function supportsObject(grip, type) {
     if (!isGrip(grip)) {
       return false;
     }
--- a/devtools/client/shared/components/reps/undefined.js
+++ b/devtools/client/shared/components/reps/undefined.js
@@ -6,32 +6,34 @@
 
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // Dependencies
   const React = require("devtools/client/shared/vendor/react");
 
+  const { wrapRender } = require("./rep-utils");
+
   // Shortcuts
   const { span } = React.DOM;
 
   /**
    * Renders undefined value
    */
   const Undefined = React.createClass({
     displayName: "UndefinedRep",
 
-    render: function () {
+    render: wrapRender(function () {
       return (
         span({className: "objectBox objectBox-undefined"},
           "undefined"
         )
       );
-    },
+    }),
   });
 
   function supportsObject(object, type) {
     if (object && object.type && object.type == "undefined") {
       return true;
     }
 
     return (type == "undefined");
--- a/devtools/client/shared/components/reps/window.js
+++ b/devtools/client/shared/components/reps/window.js
@@ -6,17 +6,21 @@
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   // ReactJS
   const React = require("devtools/client/shared/vendor/react");
 
   // Reps
-  const { isGrip, getURLDisplayString } = require("./rep-utils");
+  const {
+    isGrip,
+    getURLDisplayString,
+    wrapRender
+  } = require("./rep-utils");
 
   // Shortcuts
   const DOM = React.DOM;
 
   /**
    * Renders a grip representing a window.
    */
   let Window = React.createClass({
@@ -36,28 +40,28 @@ define(function (require, exports, modul
       }
       return "";
     },
 
     getLocation: function (grip) {
       return getURLDisplayString(grip.preview.url);
     },
 
-    render: function () {
+    render: wrapRender(function () {
       let grip = this.props.object;
 
       return (
         DOM.span({className: "objectBox objectBox-Window"},
           this.getTitle(grip),
           DOM.span({className: "objectPropValue"},
             this.getLocation(grip)
           )
         )
       );
-    },
+    }),
   });
 
   // Registration
 
   function supportsObject(object, type) {
     if (!isGrip(object)) {
       return false;
     }
--- a/devtools/client/shared/components/test/mochitest/chrome.ini
+++ b/devtools/client/shared/components/test/mochitest/chrome.ini
@@ -10,16 +10,17 @@ support-files =
 [test_reps_array.html]
 [test_reps_attribute.html]
 [test_reps_comment-node.html]
 [test_reps_date-time.html]
 [test_reps_document.html]
 [test_reps_element-node.html]
 [test_reps_error.html]
 [test_reps_event.html]
+[test_reps_failure.html]
 [test_reps_function.html]
 [test_reps_grip.html]
 [test_reps_grip-array.html]
 [test_reps_grip-map.html]
 [test_reps_infinity.html]
 [test_reps_long-string.html]
 [test_reps_nan.html]
 [test_reps_null.html]
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/test/mochitest/test_reps_failure.html
@@ -0,0 +1,60 @@
+<!-- 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/. -->
+<!DOCTYPE HTML>
+<html>
+<!--
+Test fallback for rep rendering when a rep fails to render.
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Rep test - Failure</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
+window.onload = Task.async(function* () {
+  try {
+    let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
+    let { ArrayRep } = browserRequire("devtools/client/shared/components/reps/array");
+    let { RegExp } = browserRequire("devtools/client/shared/components/reps/regexp");
+
+    // Force the RegExp rep to crash by creating RegExp grip that throws when accessing
+    // the displayString property
+    let gripStub = {
+      "type": "object",
+      "class": "RegExp",
+      "actor": "server1.conn22.obj39",
+      "extensible": true,
+      "frozen": false,
+      "sealed": false,
+      "ownPropertyLength": 1,
+      get displayString() {
+        throw new Error("failure");
+      }
+    };
+
+    // Test that correct rep is chosen.
+    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
+    is(renderedRep.type, RegExp.rep, `Rep correctly selects ${RegExp.rep.displayName}`);
+
+    // Test fallback message is displayed when rendering bad rep directly.
+    let renderedComponent = renderComponent(RegExp.rep, { object: gripStub });
+    is(renderedComponent.textContent, "Invalid object", "Fallback rendering has expected text content");
+
+    // Test fallback message is displayed when bad rep is nested in another rep.
+    renderedComponent = renderComponent(ArrayRep.rep, { object: [1, gripStub, 2] });
+    is(renderedComponent.textContent, "[ 1, Invalid object, 2 ]", "Fallback rendering has expected text content");
+  } catch(e) {
+    ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
+  } finally {
+    SimpleTest.finish();
+  }
+});
+</script>
+</pre>
+</body>
+</html>
\ No newline at end of file