author | Jason Laster <jason.laster.11@gmail.com> |
Sat, 29 Jul 2017 13:07:22 -0400 | |
changeset 371902 | 15ace180cf033edf24a4849d16247fee9ebbef22 |
parent 371901 | 00167e9fe0c0fc573801eb8a905eb3822290c2da |
child 371903 | 81c056ef9e195ac1d93ac4f4f56b04fb3614a7d5 |
push id | 47611 |
push user | archaeopteryx@coole-files.de |
push date | Sun, 30 Jul 2017 09:20:48 +0000 |
treeherder | autoland@8b577b152383 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | gtatum |
bugs | 1385421 |
milestone | 56.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/devtools/client/debugger/new/debugger.css +++ b/devtools/client/debugger/new/debugger.css @@ -354,21 +354,23 @@ body { } :root.theme-dark .CodeMirror-scrollbar-filler { background: transparent; } :root.theme-light, :root .theme-light { --search-overlays-semitransparent: rgba(221, 225, 228, 0.66); + --popup-shadow-color: #d0d0d0; } :root.theme-dark, :root .theme-dark { --search-overlays-semitransparent: rgba(42, 46, 56, 0.66); + --popup-shadow-color: #5c667b; } .debugger { display: flex; flex: 1; height: 100%; } .editor-pane { @@ -399,16 +401,17 @@ body { background-color: var(--search-overlays-semitransparent); } .search-container .close-button { width: 16px; margin-top: 25px; margin-right: 20px; } + menupopup { position: fixed; z-index: 10000; background: white; border: 1px solid #cccccc; padding: 5px 0; background: #f2f2f2; border-radius: 5px; @@ -1952,16 +1955,21 @@ html[dir="rtl"] .arrow svg, width: 350px; min-height: 80px; border: 1px solid var(--theme-splitter-color); padding: 10px; height: auto; min-height: inherit; max-height: 200px; overflow: auto; + box-shadow: 1px 2px 3px var(--popup-shadow-color); +} + +.theme-dark .popover .preview { + box-shadow: 1px 2px 3px var(--popup-shadow-color); } .popover .preview .header { width: 100%; line-height: 20px; border-bottom: 1px solid #cccccc; display: flex; flex-direction: column; @@ -2390,17 +2398,17 @@ html[dir="rtl"] .editor-mount { .breakpoints-list * { -moz-user-select: none; user-select: none; } .breakpoints-list .breakpoint { font-size: 12px; color: var(--theme-content-color1); - padding: 0.5em 12px 0.5em 5px; + padding: 0.5em 1em 0.5em 0.5em; line-height: 1em; position: relative; transition: all 0.25s ease; } html[dir="rtl"] .breakpoints-list .breakpoint { border-right: 4px solid transparent; } @@ -2505,48 +2513,57 @@ html .breakpoints-list .breakpoint.pause opacity: 1; } .input-expression:focus { outline: none; cursor: text; } +.expressions-list { + /* TODO: add normalize */ + margin: 0; + padding: 0.5em 0; +} .expression-input-container { padding: 0.5em; display: flex; } .expression-container { border: 1px; - padding: 8px 5px 0px 0px; + padding: 0.25em 1em 0.25em 0.5em; width: 100%; color: var(--theme-body-color); background-color: var(--theme-body-background); - display: flex; + display: block; position: relative; } .expression-container > .tree { width: 100%; overflow: hidden; } :root.theme-light .expression-container:hover { background-color: var(--theme-tab-toolbar-background); } :root.theme-dark .expression-container:hover { background-color: var(--search-overlays-semitransparent); } -.expression-container .close-btn { +.expression-container__close-btn { position: absolute; - offset-inline-end: 6px; - top: 6px; + offset-inline-end: 0px; + top: 4px; +} + +.expression-content { + position: relative; } .expression-container .close-btn { display: none; } .expression-container:hover .close-btn { display: block; @@ -2951,16 +2968,27 @@ html .command-bar > button:disabled { } .secondary-panes { display: flex; flex-direction: column; flex: 1; white-space: nowrap; } +/* + We apply overflow to the container with the commandbar. + This allows the commandbar to remain fixed when scrolling + until the content completely ends. Not just the height of + the wrapper. + Ref: https://github.com/devtools-html/debugger.html/issues/3426 +*/ +.secondary-panes--sticky-commandbar { + overflow-y: scroll; +} + .secondary-panes .accordion { flex: 1 0 auto; } .pane { color: var(--theme-body-color); } @@ -3192,20 +3220,24 @@ html[dir="rtl"] .dropdown { .symbol-modal { position: absolute; left: calc(50% - 250px); z-index: 10; width: 500px; height: 230px; background-color: var(--theme-codemirror-gutter-background); - box-shadow: 2px 4px 6px #dde1e4; + box-shadow: 2px 4px 6px var(--popup-shadow-color); top: 30px; } +.theme-dark .symbol-modal { + box-shadow: 2px 4px 6px var(--popup-shadow-color); +} + .symbol-modal .input-wrapper { display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-pack: center; -ms-flex-pack: center; justify-content: center; }
--- a/devtools/client/debugger/new/debugger.js +++ b/devtools/client/debugger/new/debugger.js @@ -19516,22 +19516,22 @@ return /******/ (function(modules) { // * @memberof actions/pause * @static */ function resumed() { return (_ref) => { var dispatch = _ref.dispatch, client = _ref.client; - // dispatch(evaluateExpressions(null)); - - return dispatch({ + dispatch({ type: "RESUME", value: undefined }); + + dispatch((0, _expressions.evaluateExpressions)(null)); }; } /** * Debugger has just paused * * @param {object} pauseInfo * @memberof actions/pause @@ -22480,37 +22480,28 @@ return /******/ (function(modules) { // /* 391 */ /***/ function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); - exports.isExactUrlMatch = exports.getURL = exports.getDirectories = exports.createTree = exports.collapseTree = exports.addToTree = exports.isDirectory = exports.createParentMap = exports.nodeHasChildren = exports.createNode = undefined; + exports.formatTree = exports.isExactUrlMatch = exports.getURL = exports.getDirectories = exports.createTree = exports.collapseTree = exports.addToTree = exports.isDirectory = exports.createParentMap = exports.nodeHasChildren = exports.createNode = undefined; var _url = __webpack_require__(334); - var _DevToolsUtils = __webpack_require__(222); - - var _DevToolsUtils2 = _interopRequireDefault(_DevToolsUtils); - var _source = __webpack_require__(233); var _merge = __webpack_require__(392); var _merge2 = _interopRequireDefault(_merge); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - /** - * Utils for Sources Tree Component - * @module utils/sources-tree - */ - var IGNORED_URLS = ["debugger eval code", "XStringBundle"]; /** * Temporary Source type to be used only within this module * TODO: Replace with real Source type definition when refactoring types * @memberof utils/sources-tree * @static */ @@ -22519,16 +22510,21 @@ return /******/ (function(modules) { // /** * TODO: createNode is exported so this type could be useful to other modules * @memberof utils/sources-tree * @static */ /** + * Utils for Sources Tree Component + * @module utils/sources-tree + */ + + /** * @memberof utils/sources-tree * @static */ function nodeHasChildren(item) { return Array.isArray(item.contents); } /** @@ -22686,17 +22682,23 @@ return /******/ (function(modules) { // var isLastPart = i === parts.length - 1; // Currently we assume that we are descending into a node with // children. This will fail if a path has a directory named the // same as another file, like `foo/bar.js/file.js`. // // TODO: Be smarter about this, which we'll probably do when we // are smarter about folders and collapsing empty ones. - (0, _DevToolsUtils2.default)(nodeHasChildren(subtree), `${subtree.name} should have children`); + + if (!nodeHasChildren(subtree)) { + return { + v: void 0 + }; + } + var children = subtree.contents; var index = determineFileSortOrder(children, part, isLastPart, i === 0 ? debuggeeUrl : ""); var child = children.find(c => c.name === part); if (child) { // A node with the same name already exists, simply traverse // into it. @@ -22710,17 +22712,19 @@ return /******/ (function(modules) { // subtree = children[where]; } // Keep track of the children so we can tag each node with them. path = `${path}/${part}`; }; for (var i = 0; i < parts.length; i++) { - _loop(i); + var _ret = _loop(i); + + if (typeof _ret === "object") return _ret.v; } // Overwrite the contents of the final node to store the source // there. if (!isDir) { subtree.contents = source; } else if (!subtree.contents.find(c => c.name === "(index)")) { subtree.contents.unshift(createNode("(index)", source.get("url"), source)); @@ -22866,26 +22870,50 @@ return /******/ (function(modules) { // node = parentMap.get(node); if (!node) { return directories; } directories.push(node); } } + function formatTree(tree) { + var depth = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; + var str = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : ""; + + var whitespace = new Array(depth * 2).join(" "); + + if (!tree.contents) { + return str; + } + + if (tree.contents.length > 0) { + str += `${whitespace} - ${tree.name} path=${tree.path} \n`; + tree.contents.forEach(t => { + str = formatTree(t, depth + 1, str); + }); + } else if (tree.contents.toJS) { + var id = tree.contents.get("id"); + str += `${whitespace} - ${tree.name} path=${tree.path} source_id=${id} \n`; + } + + return str; + } + exports.createNode = createNode; exports.nodeHasChildren = nodeHasChildren; exports.createParentMap = createParentMap; exports.isDirectory = isDirectory; exports.addToTree = addToTree; exports.collapseTree = collapseTree; exports.createTree = createTree; exports.getDirectories = getDirectories; exports.getURL = getURL; exports.isExactUrlMatch = isExactUrlMatch; + exports.formatTree = formatTree; /***/ }, /* 392 */ /***/ function(module, exports, __webpack_require__) { var baseMerge = __webpack_require__(393), createAssigner = __webpack_require__(410); @@ -24249,17 +24277,17 @@ return /******/ (function(modules) { // shortcuts.on("CmdOrCtrl+B", this.onToggleBreakpoint); shortcuts.on("CmdOrCtrl+Shift+B", this.onToggleBreakpoint); shortcuts.on("Esc", this.onEscape); shortcuts.on(searchAgainPrevKey, this.onSearchAgain); shortcuts.on(searchAgainKey, this.onSearchAgain); if (selectedLocation && !!selectedLocation.line) { - this.pendingJumpLocation = selectedLocation; + this.pendingJumpLine = selectedLocation.line; } (0, _editor.updateDocument)(editor, selectedSource); } componentWillUnmount() { this.state.editor.destroy(); this.setState({ editor: null }); @@ -24478,17 +24506,18 @@ return /******/ (function(modules) { // var condition = breakpoint ? breakpoint.condition : ""; var panel = (0, _ConditionalPanel.renderConditionalPanel)({ condition, setBreakpoint: value => setBreakpointCondition(location, { condition: value }), closePanel: this.closeConditionalPanel }); - this.cbPanel = this.state.editor.codeMirror.addLineWidget(line, panel, { + var editorLine = line - 1; + this.cbPanel = this.state.editor.codeMirror.addLineWidget(editorLine, panel, { coverGutter: true, noHScroll: false }); this.cbPanel.node.querySelector("input").focus(); } closeConditionalPanel() { this.cbPanel.clear(); @@ -28092,16 +28121,17 @@ return /******/ (function(modules) { // type: "checkbox", "aria-label": breakpointsDisabled ? L10N.getStr("breakpoints.enable") : L10N.getStr("breakpoints.disable"), className: boxClassName, disabled: breakpointsLoading, onChange: e => { e.stopPropagation(); toggleAllBreakpoints(!breakpointsDisabled); }, + onClick: e => e.stopPropagation(), checked: !breakpointsDisabled && !isIndeterminate, ref: input => { if (input) { input.indeterminate = isIndeterminate; } }, title: breakpointsDisabled ? L10N.getStr("breakpoints.enable") : L10N.getStr("breakpoints.disable") }); @@ -28203,17 +28233,17 @@ return /******/ (function(modules) { // splitterSize: 1, startPanel: Accordion({ items: this.getStartItems() }), endPanel: Accordion({ items: this.getEndItems() }) }); } render() { return _react.DOM.div({ - className: "secondary-panes" + className: "secondary-panes secondary-panes--sticky-commandbar" }, CommandBar(), this.props.horizontal ? this.renderHorizontalLayout() : this.renderVerticalLayout()); } } SecondaryPanes.propTypes = { evaluateExpressions: _react.PropTypes.func.isRequired, pauseData: _react.PropTypes.object, horizontal: _react.PropTypes.bool, @@ -28414,26 +28444,28 @@ return /******/ (function(modules) { // } var root = { name: expression.input, path, contents: { value } }; - return _react.DOM.div({ + return _react.DOM.li({ className: "expression-container", key: `${path}/${input}` - }, ObjectInspector({ + }, _react.DOM.div({ className: "expression-content" }, ObjectInspector({ roots: [root], getObjectProperties: id => loadedObjects[id], autoExpandDepth: 0, onDoubleClick: (item, options) => this.editExpression(expression, options), loadObjectProperties - }), CloseButton({ handleClick: e => this.deleteExpression(e, expression) })); + }), _react.DOM.div({ className: "expression-container__close-btn" }, CloseButton({ + handleClick: e => this.deleteExpression(e, expression) + })))); } componentDidUpdate() { if (this._input) { this._input.focus(); } } @@ -28447,31 +28479,31 @@ return /******/ (function(modules) { // if (value == "") { return; } e.stopPropagation(); e.target.value = ""; this.props.addExpression(value); }; - return _react.DOM.span({ className: "expression-input-container" }, _react.DOM.input({ + return _react.DOM.li({ className: "expression-input-container" }, _react.DOM.input({ type: "text", className: "input-expression", placeholder: L10N.getStr("expressions.placeholder"), onBlur: e => { e.target.value = ""; }, onKeyPress })); } render() { var expressions = this.props.expressions; - return _react.DOM.span({ className: "pane expressions-list" }, expressions.map(this.renderExpression), this.renderNewExpressionInput()); + return _react.DOM.ul({ className: "pane expressions-list" }, expressions.map(this.renderExpression), this.renderNewExpressionInput()); } } Expressions.displayName = "Expressions"; exports.default = (0, _reactRedux.connect)(state => ({ pauseInfo: (0, _selectors.getPause)(state), expressions: (0, _selectors.getVisibleExpressions)(state), @@ -45926,19 +45958,17 @@ return /******/ (function(modules) { // "use strict"; /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ function forEachLine(codeMirror, iter) { - codeMirror.operation(() => { - codeMirror.doc.iter(0, codeMirror.lineCount(), iter); - }); + codeMirror.doc.iter(0, codeMirror.lineCount(), iter); } function removeLineClass(codeMirror, line, className) { codeMirror.removeLineClass(line, "line", className); } function clearLineClass(codeMirror, className) { forEachLine(codeMirror, line => { @@ -45949,33 +45979,16 @@ return /******/ (function(modules) { // function getTextForLine(codeMirror, line) { return codeMirror.getLine(line - 1).trim(); } function getCursorLine(codeMirror) { return codeMirror.getCursor().line; } - function getTokenLocation(codeMirror, tokenEl) { - var lineOffset = 1; - - var _tokenEl$getBoundingC = tokenEl.getBoundingClientRect(), - left = _tokenEl$getBoundingC.left, - top = _tokenEl$getBoundingC.top; - - var _codeMirror$coordsCha = codeMirror.coordsChar({ left, top }), - line = _codeMirror$coordsCha.line, - ch = _codeMirror$coordsCha.ch; - - return { - line: line + lineOffset, - column: ch - }; - } - /** * Forces the breakpoint gutter to be the same size as the line * numbers gutter. Editor CSS will absolutely position the gutter * beneath the line numbers. This makes it easy to be flexible with * how we overlay breakpoints. */ function resizeBreakpointGutter(editor) { var gutters = editor.display.gutters; @@ -45984,17 +45997,16 @@ return /******/ (function(modules) { // breakpoints.style.width = `${lineNumbers.clientWidth}px`; } module.exports = { removeLineClass, clearLineClass, getTextForLine, getCursorLine, - getTokenLocation, resizeBreakpointGutter }; /***/ }, /* 997 */ /***/ function(module, exports) { module.exports = "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.1\" viewBox=\"0 0 32 32\"><script></script><path fill=\"#444444\" d=\"M16 9.875l-9.539-5.438v23.698l9.539-5.438 9.539 5.438v-23.698l-9.539 5.438zM11.248 16.286l4.752-2.709 4.752 2.709-4.752 2.709-4.752-2.709zM9.618 9.643l3.399 1.938-3.399 1.938v-3.876zM9.618 19.053l3.145 1.792-3.145 1.793v-3.585zM22.382 22.638l-3.145-1.793 3.145-1.793v3.585zM18.982 11.581l3.399-1.938v3.876l-3.399-1.938z\"></path></svg>"
--- a/devtools/client/debugger/new/search-worker.js +++ b/devtools/client/debugger/new/search-worker.js @@ -778,29 +778,36 @@ return /******/ (function(modules) { // exports.default = getMatches; var _buildQuery = __webpack_require__(1138); var _buildQuery2 = _interopRequireDefault(_buildQuery); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + var MAX_LENGTH = 100000; + function getMatches(query, text, modifiers) { if (!query || !text || !modifiers) { return []; } var regexQuery = (0, _buildQuery2.default)(query, modifiers, { isGlobal: true }); var matchedLocations = []; var lines = text.split("\n"); for (var i = 0; i < lines.length; i++) { var singleMatch = void 0; - while ((singleMatch = regexQuery.exec(lines[i])) !== null) { - matchedLocations.push({ line: i, ch: singleMatch.index }); + var line = lines[i]; + if (line.length <= MAX_LENGTH) { + while ((singleMatch = regexQuery.exec(line)) !== null) { + matchedLocations.push({ line: i, ch: singleMatch.index }); + } + } else { + return []; } } return matchedLocations; } /***/ } /******/ })
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints-cond.js +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints-cond.js @@ -2,16 +2,33 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ function findBreakpoint(dbg, url, line) { const { selectors: { getBreakpoint }, getState } = dbg; const source = findSource(dbg, url); return getBreakpoint(getState(), { sourceId: source.id, line }); } +function getLineEl(dbg, line) { + const lines = dbg.win.document.querySelectorAll(".CodeMirror-code > div"); + return lines[line - 1]; +} + +function assertEditorBreakpoint(dbg, line, shouldExist) { + const exists = getLineEl(dbg, line).classList.contains("has-condition"); + + ok( + exists === shouldExist, + "Breakpoint " + + (shouldExist ? "exists" : "does not exist") + + " on line " + + line + ); +} + function setConditionalBreakpoint(dbg, index, condition) { return Task.spawn(function*() { rightClickElement(dbg, "gutter", index); selectMenuItem(dbg, 2); yield waitForElement(dbg, ".conditional-breakpoint-panel input"); findElementWithSelector(dbg, ".conditional-breakpoint-panel input").focus(); // Position cursor reliably at the end of the text. pressKey(dbg, "End"); @@ -24,30 +41,34 @@ add_task(function*() { const dbg = yield initDebugger("doc-scripts.html"); yield selectSource(dbg, "simple2"); // Adding a conditional Breakpoint yield setConditionalBreakpoint(dbg, 5, "1"); yield waitForDispatch(dbg, "ADD_BREAKPOINT"); let bp = findBreakpoint(dbg, "simple2", 5); is(bp.condition, "1", "breakpoint is created with the condition"); + assertEditorBreakpoint(dbg, 5, true); // Editing a conditional Breakpoint yield setConditionalBreakpoint(dbg, 5, "2"); yield waitForDispatch(dbg, "SET_BREAKPOINT_CONDITION"); bp = findBreakpoint(dbg, "simple2", 5); is(bp.condition, "12", "breakpoint is created with the condition"); + assertEditorBreakpoint(dbg, 5, true); // Removing a conditional breakpoint clickElement(dbg, "gutter", 5); yield waitForDispatch(dbg, "REMOVE_BREAKPOINT"); bp = findBreakpoint(dbg, "simple2", 5); is(bp, null, "breakpoint was removed"); + assertEditorBreakpoint(dbg, 5, false); // Adding a condition to a breakpoint clickElement(dbg, "gutter", 5); yield waitForDispatch(dbg, "ADD_BREAKPOINT"); yield setConditionalBreakpoint(dbg, 5, "1"); yield waitForDispatch(dbg, "SET_BREAKPOINT_CONDITION"); bp = findBreakpoint(dbg, "simple2", 5); is(bp.condition, "1", "breakpoint is created with the condition"); + assertEditorBreakpoint(dbg, 5, true); });
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints-reloading.js +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints-reloading.js @@ -19,16 +19,18 @@ function addBreakpoint(dbg, line) { } function assertEditorBreakpoint(dbg, line) { const exists = !!getLineEl(dbg, line).querySelector(".new-breakpoint"); ok(exists, `Breakpoint exists on line ${line}`); } add_task(function*() { + requestLongerTimeout(2); + const dbg = yield initDebugger("doc-scripts.html"); const { selectors: { getBreakpoints, getBreakpoint }, getState } = dbg; const source = findSource(dbg, "simple1.js"); yield selectSource(dbg, source.url); yield addBreakpoint(dbg, 5); yield addBreakpoint(dbg, 2);
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-expressions.js +++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-expressions.js @@ -15,16 +15,20 @@ const expressionSelectors = { function getLabel(dbg, index) { return findElement(dbg, "expressionNode", index).innerText; } function getValue(dbg, index) { return findElement(dbg, "expressionValue", index).innerText; } +function toggleExpression(dbg, index) { + findElement(dbg, "expressionNode", index).click(); +} + async function addExpression(dbg, input) { info("Adding an expression"); findElementWithSelector(dbg, expressionSelectors.input).focus(); type(dbg, input); pressKey(dbg, "Enter"); await waitForDispatch(dbg, "EVALUATE_EXPRESSION"); } @@ -48,11 +52,20 @@ add_task(function*() { yield addExpression(dbg, "f"); is(getLabel(dbg, 1), "f"); is(getValue(dbg, 1), "(unavailable)"); yield editExpression(dbg, "oo"); is(getLabel(dbg, 1), "foo()"); is(getValue(dbg, 1), ""); + yield addExpression(dbg, "location"); + is(getLabel(dbg, 2), "location"); + ok(getValue(dbg, 2).includes("Location"), "has a value"); + + // can expand an expression + toggleExpression(dbg, 2); + yield waitForDispatch(dbg, "LOAD_OBJECT_PROPERTIES"); + yield deleteExpression(dbg, "foo"); + yield deleteExpression(dbg, "location"); is(findAllElements(dbg, "expressionNodes").length, 0); });
--- a/devtools/client/debugger/new/test/mochitest/head.js +++ b/devtools/client/debugger/new/test/mochitest/head.js @@ -648,19 +648,19 @@ function isVisibleWithin(outerEl, innerE const outerRect = outerEl.getBoundingClientRect(); return innerRect.top > outerRect.top && innerRect.bottom < outerRect.bottom; } const selectors = { callStackHeader: ".call-stack-pane ._header", callStackBody: ".call-stack-pane .pane", expressionNode: i => - `.expressions-list .tree-node:nth-child(${i}) .object-label`, + `.expressions-list .expression-container:nth-child(${i}) .object-label`, expressionValue: i => - `.expressions-list .tree-node:nth-child(${i}) .object-value`, + `.expressions-list .expression-container:nth-child(${i}) .object-value`, expressionClose: i => `.expressions-list .expression-container:nth-child(${i}) .close`, expressionNodes: ".expressions-list .tree-node", scopesHeader: ".scopes-pane ._header", breakpointItem: i => `.breakpoints-list .breakpoint:nth-child(${i})`, scopeNode: i => `.scopes-list .tree-node:nth-child(${i}) .object-label`, scopeValue: i => `.scopes-list .tree-node:nth-child(${i}) .object-value`, frame: i => `.frames ul li:nth-child(${i})`,