Bug 1313119 - wrap all rep render() methods with try/catch;r=nchevobbe

MozReview-Commit-ID: 5nXcv3in4WT

--HG--
extra : rebase_source : 1f13b2995a20fe12d3c29e9138f7bbd382fac9d4
This commit is contained in:
Julian Descottes 2016-12-30 18:12:38 +01:00
parent ae77a86895
commit 7ebaf882eb
34 changed files with 280 additions and 87 deletions

View File

@ -10,7 +10,10 @@
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");
@ -116,7 +119,7 @@ define(function (require, exports, module) {
onClickBracket: function (event) {
},
render: function () {
render: wrapRender(function () {
let {
object,
mode = MODE.SHORT,
@ -158,7 +161,7 @@ define(function (require, exports, module) {
)
)
);
},
}),
});
/**
@ -167,7 +170,7 @@ define(function (require, exports, module) {
let ItemRep = React.createFactory(React.createClass({
displayName: "ItemRep",
render: function () {
render: wrapRender(function () {
const { Rep } = createFactories(require("./rep"));
let object = this.props.object;
@ -179,7 +182,7 @@ define(function (require, exports, module) {
delim
)
);
}
})
}));
function supportsObject(object, type) {

View File

@ -11,7 +11,11 @@ define(function (require, exports, module) {
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
@ -32,7 +36,7 @@ define(function (require, exports, module) {
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;
@ -50,7 +54,7 @@ define(function (require, exports, module) {
)
)
);
},
}),
});
// Registration

View File

@ -12,6 +12,8 @@ define(function (require, exports, module) {
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.
@ -19,11 +21,11 @@ define(function (require, exports, module) {
const Caption = React.createClass({
displayName: "Caption",
render: function () {
render: wrapRender(function () {
return (
DOM.span({"className": "caption"}, this.props.object)
);
},
}),
});
// Exports from this module

View File

@ -9,7 +9,12 @@
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");
@ -28,7 +33,7 @@ define(function (require, exports, module) {
mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
},
render: function () {
render: wrapRender(function () {
let {
object,
mode = MODE.SHORT
@ -42,7 +47,7 @@ define(function (require, exports, module) {
}
return span({className: "objectBox theme-comment"}, `<!-- ${textContent} -->`);
},
}),
});
// Registration

View File

@ -11,7 +11,10 @@ define(function (require, exports, module) {
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;
@ -35,7 +38,7 @@ define(function (require, exports, module) {
return "";
},
render: function () {
render: wrapRender(function () {
let grip = this.props.object;
let date;
try {
@ -48,8 +51,9 @@ define(function (require, exports, module) {
} catch (e) {
date = span({className: "objectBox"}, "Invalid Date");
}
return date;
},
}),
});
// Registration

View File

@ -11,7 +11,11 @@ define(function (require, exports, module) {
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;
@ -46,7 +50,7 @@ define(function (require, exports, module) {
return doc.location.href;
},
render: function () {
render: wrapRender(function () {
let grip = this.props.object;
return (
@ -57,7 +61,7 @@ define(function (require, exports, module) {
)
)
);
},
}),
});
// Registration

View File

@ -11,7 +11,10 @@ define(function (require, exports, module) {
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");
@ -88,7 +91,7 @@ define(function (require, exports, module) {
];
},
render: function () {
render: wrapRender(function () {
let {
object,
mode,
@ -114,7 +117,7 @@ define(function (require, exports, module) {
return objectLink({object},
span(baseConfig, ...elements)
);
},
}),
});
// Registration

View File

@ -8,7 +8,10 @@ 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;
@ -25,7 +28,7 @@ define(function (require, exports, module) {
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
@ -51,7 +54,7 @@ define(function (require, exports, module) {
span({}, content)
)
);
},
}),
});
// Registration

View File

@ -11,7 +11,11 @@ define(function (require, exports, module) {
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);
/**
@ -34,7 +38,7 @@ define(function (require, exports, module) {
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.
@ -80,7 +84,7 @@ define(function (require, exports, module) {
}
return rep(props);
}
})
});
// Registration

View File

@ -11,7 +11,11 @@ define(function (require, exports, module) {
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;
@ -40,7 +44,7 @@ define(function (require, exports, module) {
return cropString(name + "()", 100);
},
render: function () {
render: wrapRender(function () {
let grip = this.props.object;
return (
@ -51,7 +55,7 @@ define(function (require, exports, module) {
this.summarizeFunction(grip)
)
);
},
}),
});
// Registration

View File

@ -10,7 +10,11 @@
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");
@ -109,7 +113,7 @@ define(function (require, exports, module) {
return items;
},
render: function () {
render: wrapRender(function () {
let {
object,
mode = MODE.SHORT
@ -154,7 +158,7 @@ define(function (require, exports, module) {
)
)
);
},
}),
});
/**

View File

@ -9,7 +9,11 @@
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");
@ -144,7 +148,7 @@ define(function (require, exports, module) {
}, []);
},
render: function () {
render: wrapRender(function () {
let object = this.props.object;
let props = this.safeEntriesIterator(object,
(this.props.mode === MODE.LONG) ? 10 : 3);
@ -176,7 +180,7 @@ define(function (require, exports, module) {
}, " }")
)
);
},
}),
});
function supportsObject(grip, type) {

View File

@ -10,7 +10,11 @@ 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");
@ -198,7 +202,7 @@ define(function (require, exports, module) {
return value;
},
render: function () {
render: wrapRender(function () {
let object = this.props.object;
let props = this.safePropIterator(object,
(this.props.mode === MODE.LONG) ? 10 : 3);
@ -230,7 +234,7 @@ define(function (require, exports, module) {
}, " }")
)
);
},
}),
});
// Registration

View File

@ -11,6 +11,8 @@ define(function (require, exports, module) {
// Dependencies
const React = require("devtools/client/shared/vendor/react");
const { wrapRender } = require("./rep-utils");
// Shortcuts
const { span } = React.DOM;
@ -20,13 +22,13 @@ define(function (require, exports, module) {
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) {

View File

@ -8,7 +8,11 @@
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;
@ -29,7 +33,7 @@ define(function (require, exports, module) {
};
},
render: function () {
render: wrapRender(function () {
let {
cropLimit,
member,
@ -53,7 +57,7 @@ define(function (require, exports, module) {
}
let formattedString = useQuotes ? `"${string}"` : string;
return span(config, sanitizeString(formattedString));
},
}),
});
function supportsObject(object, type) {

View File

@ -11,6 +11,8 @@ define(function (require, exports, module) {
// Dependencies
const React = require("devtools/client/shared/vendor/react");
const { wrapRender } = require("./rep-utils");
// Shortcuts
const { span } = React.DOM;
@ -20,13 +22,13 @@ define(function (require, exports, module) {
const NaNRep = React.createClass({
displayName: "NaN",
render: function () {
render: wrapRender(function () {
return (
span({className: "objectBox objectBox-nan"},
"NaN"
)
);
}
})
});
function supportsObject(object, type) {

View File

@ -11,6 +11,8 @@ define(function (require, exports, module) {
// Dependencies
const React = require("devtools/client/shared/vendor/react");
const { wrapRender } = require("./rep-utils");
// Shortcuts
const { span } = React.DOM;
@ -20,13 +22,13 @@ define(function (require, exports, module) {
const Null = React.createClass({
displayName: "NullRep",
render: function () {
render: wrapRender(function () {
return (
span({className: "objectBox objectBox-null"},
"null"
)
);
},
}),
});
function supportsObject(object, type) {

View File

@ -11,6 +11,8 @@ define(function (require, exports, module) {
// Dependencies
const React = require("devtools/client/shared/vendor/react");
const { wrapRender } = require("./rep-utils");
// Shortcuts
const { span } = React.DOM;
@ -27,7 +29,7 @@ define(function (require, exports, module) {
return (isNegativeZero ? "-0" : String(object));
},
render: function () {
render: wrapRender(function () {
let value = this.props.object;
return (
@ -35,7 +37,7 @@ define(function (require, exports, module) {
this.stringify(value)
)
);
}
})
});
function supportsObject(object, type) {

View File

@ -11,7 +11,10 @@ define(function (require, exports, module) {
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;
@ -45,7 +48,7 @@ define(function (require, exports, module) {
return "\"" + grip.preview.text + "\"";
},
render: function () {
render: wrapRender(function () {
let grip = this.props.object;
return (
span({className: "objectBox objectBox-" + this.getType(grip)},
@ -55,7 +58,7 @@ define(function (require, exports, module) {
)
)
);
},
}),
});
// Registration

View File

@ -11,7 +11,11 @@ define(function (require, exports, module) {
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;
@ -45,7 +49,7 @@ define(function (require, exports, module) {
return getURLDisplayString(grip.preview.url);
},
render: function () {
render: wrapRender(function () {
let grip = this.props.object;
return (
span({className: "objectBox objectBox-" + this.getType(grip)},
@ -55,7 +59,7 @@ define(function (require, exports, module) {
)
)
);
},
}),
});
// Registration

View File

@ -9,7 +9,10 @@
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");
@ -132,7 +135,7 @@ define(function (require, exports, module) {
return props;
},
render: function () {
render: wrapRender(function () {
let object = this.props.object;
let props = this.safePropIterator(object);
let objectLink = this.props.objectLink || span;
@ -159,7 +162,7 @@ define(function (require, exports, module) {
}, " }")
)
);
},
}),
});
function supportsObject(object, type) {
return true;

View File

@ -10,7 +10,12 @@ 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
@ -55,7 +60,7 @@ define(function (require, exports, module) {
});
},
render: function () {
render: wrapRender(function () {
const object = this.props.object;
const {promiseState} = object;
let objectLink = this.props.objectLink || span;
@ -94,7 +99,7 @@ define(function (require, exports, module) {
}, " }")
)
);
},
}),
});
// Registration

View File

@ -9,7 +9,10 @@
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;
@ -19,7 +22,7 @@ define(function (require, exports, module) {
* 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: {
@ -36,7 +39,7 @@ define(function (require, exports, module) {
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"));
@ -66,8 +69,8 @@ define(function (require, exports, module) {
}, this.props.delim)
)
);
}
}));
})
});
// Exports from this module
exports.PropRep = PropRep;

View File

@ -11,7 +11,10 @@ define(function (require, exports, module) {
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;
@ -30,7 +33,7 @@ define(function (require, exports, module) {
return grip.displayString;
},
render: function () {
render: wrapRender(function () {
let grip = this.props.object;
let objectLink = this.props.objectLink || span;
@ -42,7 +45,7 @@ define(function (require, exports, module) {
}, this.getSource(grip))
)
);
},
}),
});
// Registration

View File

@ -147,6 +147,27 @@ define(function (require, exports, module) {
};
}
/**
* 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;
@ -156,5 +177,6 @@ define(function (require, exports, module) {
exports.parseURLEncodedText = parseURLEncodedText;
exports.getFileName = getFileName;
exports.getURLDisplayString = getURLDisplayString;
exports.wrapRender = wrapRender;
exports.sanitizeString = sanitizeString;
});

View File

@ -96,6 +96,15 @@
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,

View File

@ -10,7 +10,11 @@
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;
@ -32,7 +36,7 @@ define(function (require, exports, module) {
};
},
render: function () {
render: wrapRender(function () {
let text = this.props.object;
let member = this.props.member;
let style = this.props.style;
@ -53,7 +57,7 @@ define(function (require, exports, module) {
"\"" + croppedString + "\"" : croppedString;
return span(config, formattedString);
},
}),
});
function supportsObject(object, type) {

View File

@ -11,7 +11,11 @@ define(function (require, exports, module) {
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;
@ -44,7 +48,7 @@ define(function (require, exports, module) {
return url ? getURLDisplayString(url) : "";
},
render: function () {
render: wrapRender(function () {
let grip = this.props.object;
return (
@ -55,7 +59,7 @@ define(function (require, exports, module) {
)
)
);
},
}),
});
// Registration

View File

@ -11,6 +11,8 @@ define(function (require, exports, module) {
// Dependencies
const React = require("devtools/client/shared/vendor/react");
const { wrapRender } = require("./rep-utils");
// Shortcuts
const { span } = React.DOM;
@ -24,7 +26,7 @@ define(function (require, exports, module) {
object: React.PropTypes.object.isRequired
},
render: function () {
render: wrapRender(function () {
let {object} = this.props;
let {name} = object;
@ -33,7 +35,7 @@ define(function (require, exports, module) {
`Symbol(${name || ""})`
)
);
},
}),
});
function supportsObject(object, type) {

View File

@ -11,7 +11,11 @@ define(function (require, exports, module) {
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
@ -43,7 +47,7 @@ define(function (require, exports, module) {
return title;
},
render: function () {
render: wrapRender(function () {
let {
object: grip,
mode = MODE.SHORT,
@ -75,7 +79,7 @@ define(function (require, exports, module) {
)
)
);
},
}),
});
// Registration

View File

@ -11,6 +11,8 @@ define(function (require, exports, module) {
// Dependencies
const React = require("devtools/client/shared/vendor/react");
const { wrapRender } = require("./rep-utils");
// Shortcuts
const { span } = React.DOM;
@ -20,13 +22,13 @@ define(function (require, exports, module) {
const Undefined = React.createClass({
displayName: "UndefinedRep",
render: function () {
render: wrapRender(function () {
return (
span({className: "objectBox objectBox-undefined"},
"undefined"
)
);
},
}),
});
function supportsObject(object, type) {

View File

@ -11,7 +11,11 @@ define(function (require, exports, module) {
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;
@ -41,7 +45,7 @@ define(function (require, exports, module) {
return getURLDisplayString(grip.preview.url);
},
render: function () {
render: wrapRender(function () {
let grip = this.props.object;
return (
@ -52,7 +56,7 @@ define(function (require, exports, module) {
)
)
);
},
}),
});
// Registration

View File

@ -15,6 +15,7 @@ support-files =
[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]

View File

@ -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>