Bug 1385421 - Upgrade Debugger Frontend 7/28 (0.11). r=gtatum
authorJason Laster <jason.laster.11@gmail.com>
Sat, 29 Jul 2017 13:07:22 -0400
changeset 371902 15ace180cf033edf24a4849d16247fee9ebbef22
parent 371901 00167e9fe0c0fc573801eb8a905eb3822290c2da
child 371903 81c056ef9e195ac1d93ac4f4f56b04fb3614a7d5
push id47611
push userarchaeopteryx@coole-files.de
push dateSun, 30 Jul 2017 09:20:48 +0000
treeherderautoland@8b577b152383 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgtatum
bugs1385421
milestone56.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
Bug 1385421 - Upgrade Debugger Frontend 7/28 (0.11). r=gtatum
devtools/client/debugger/new/debugger.css
devtools/client/debugger/new/debugger.js
devtools/client/debugger/new/search-worker.js
devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints-cond.js
devtools/client/debugger/new/test/mochitest/browser_dbg-breakpoints-reloading.js
devtools/client/debugger/new/test/mochitest/browser_dbg-expressions.js
devtools/client/debugger/new/test/mochitest/head.js
--- 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})`,