Bug 1597946 - Browser debugger goes blank. r=nchevobbe
authorJason Laster <jlaster@mozilla.com>
Tue, 26 Nov 2019 17:42:14 +0000
changeset 503876 8127325c6941e37795b00757864aefef3f2eb9b3
parent 503875 d89bbe494f6b6f1fc9839bdb204afaf64d42f75c
child 503877 aa497f0dabfee41f923665da6c1e60dcece90f44
push id36849
push userdvarga@mozilla.com
push dateTue, 26 Nov 2019 21:27:08 +0000
treeherdermozilla-central@bcbb45f7d4f1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnchevobbe
bugs1597946
milestone72.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 1597946 - Browser debugger goes blank. r=nchevobbe Differential Revision: https://phabricator.services.mozilla.com/D54001
devtools/client/debugger/packages/devtools-reps/src/object-inspector/components/ObjectInspector.js
devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/utils/should-render-roots-in-reps.js
devtools/client/debugger/packages/devtools-reps/src/object-inspector/utils/index.js
devtools/client/debugger/src/components/Editor/Preview/Popup.js
devtools/client/debugger/src/reducers/preview.js
devtools/client/debugger/test/mochitest/browser_dbg-preview.js
devtools/client/debugger/test/mochitest/examples/preview.js
devtools/client/locales/en-US/debugger.properties
devtools/client/shared/components/reps/reps.js
--- a/devtools/client/debugger/packages/devtools-reps/src/object-inspector/components/ObjectInspector.js
+++ b/devtools/client/debugger/packages/devtools-reps/src/object-inspector/components/ObjectInspector.js
@@ -318,14 +318,19 @@ function mapStateToProps(state, props) {
 
 const OI = connect(
   mapStateToProps,
   actions
 )(ObjectInspector);
 
 module.exports = (props: Props) => {
   const { roots } = props;
+
+  if (roots.length == 0) {
+    return null;
+  }
+
   if (shouldRenderRootsInReps(roots)) {
     return renderRep(roots[0], props);
   }
 
   return createElement(OI, props);
 };
--- a/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/utils/should-render-roots-in-reps.js
+++ b/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/utils/should-render-roots-in-reps.js
@@ -30,16 +30,20 @@ describe("shouldRenderRootsInReps", () =
       shouldRenderRootsInReps([
         {
           contents: { value: numberStubs.get("Int") },
         },
       ])
     ).toBeTruthy();
   });
 
+  it("returns false for empty roots", () => {
+    expect(shouldRenderRootsInReps([])).toBeFalsy();
+  });
+
   it("returns true for a big int", () => {
     expect(
       shouldRenderRootsInReps([
         {
           contents: { value: bigIntStubs.get("1n") },
         },
       ])
     ).toBeTruthy();
--- a/devtools/client/debugger/packages/devtools-reps/src/object-inspector/utils/index.js
+++ b/devtools/client/debugger/packages/devtools-reps/src/object-inspector/utils/index.js
@@ -10,17 +10,17 @@ const selection = require("./selection")
 
 const { MODE } = require("../../reps/constants");
 const {
   REPS: { Rep, Grip },
 } = require("../../reps/rep");
 import type { Node, Props } from "../types";
 
 function shouldRenderRootsInReps(roots: Array<Node>): boolean {
-  if (roots.length > 1) {
+  if (roots.length !== 1) {
     return false;
   }
 
   const root = roots[0];
   const name = root && root.name;
   return (
     (name === null || typeof name === "undefined") &&
     (nodeIsPrimitive(root) || nodeIsError(root))
--- a/devtools/client/debugger/src/components/Editor/Preview/Popup.js
+++ b/devtools/client/debugger/src/components/Editor/Preview/Popup.js
@@ -124,16 +124,24 @@ export class Popup extends Component<Pro
     const {
       preview: { properties },
       openLink,
       openElementInInspector,
       highlightDomElement,
       unHighlightDomElement,
     } = this.props;
 
+    if (properties.length == 0) {
+      return (
+        <div className="preview-popup">
+          <span className="label">{L10N.getStr("preview.noProperties")}</span>
+        </div>
+      );
+    }
+
     return (
       <div
         className="preview-popup"
         style={{ maxHeight: this.calculateMaxHeight() }}
       >
         <ObjectInspector
           roots={properties}
           autoExpandDepth={0}
@@ -183,19 +191,24 @@ export class Popup extends Component<Pro
       return <div>{this.renderObjectPreview()}</div>;
     }
 
     return this.renderSimplePreview();
   }
 
   getPreviewType() {
     const {
-      preview: { root },
+      preview: { root, properties },
     } = this.props;
-    if (nodeIsPrimitive(root) || nodeIsFunction(root)) {
+    if (
+      nodeIsPrimitive(root) ||
+      nodeIsFunction(root) ||
+      !Array.isArray(properties) ||
+      properties.length === 0
+    ) {
       return "tooltip";
     }
 
     return "popover";
   }
 
   onMouseOut = () => {
     const { clearPreview, cx } = this.props;
--- a/devtools/client/debugger/src/reducers/preview.js
+++ b/devtools/client/debugger/src/reducers/preview.js
@@ -2,23 +2,23 @@
  * 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/>. */
 
 // @flow
 
 import type { AstLocation } from "../workers/parser";
 
 import type { Action } from "../actions/types";
-import type { Node, Grip, GripProperties } from "devtools-reps";
+import type { Node, Grip } from "devtools-reps";
 
 export type Preview = {|
   expression: string,
   result: Grip,
   root: Node,
-  properties: GripProperties,
+  properties: Array<Grip>,
   location: AstLocation,
   cursorPos: any,
   tokenPos: AstLocation,
   target: HTMLDivElement,
 |};
 
 export type PreviewState = {
   +preview: ?Preview,
--- a/devtools/client/debugger/test/mochitest/browser_dbg-preview.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg-preview.js
@@ -10,17 +10,17 @@ async function previews(dbg, fnName, pre
   await resume(dbg);
 
   info(`Ran tests for ${fnName}`);
 }
 
 async function testBucketedArray(dbg) {
   const invokeResult = invokeInTab("largeArray");
   await waitForPaused(dbg);
-  const preview = await hoverOnToken(dbg, 27, 8, "popup");
+  const preview = await hoverOnToken(dbg, 33, 10, "popup");
 
   is(
     preview.properties.map(p => p.name).join(" "),
     "[0…99] [100…100] length <prototype>",
     "Popup properties are bucketed"
   );
 
   is(preview.properties[0].meta.endIndex, 99, "first bucket ends at 99");
@@ -32,26 +32,30 @@ async function testBucketedArray(dbg) {
 // simple value, which will show a tooltip.
 add_task(async function() {
   const dbg = await initDebugger("doc-preview.html", "preview.js");
   await selectSource(dbg, "preview.js");
 
   await testBucketedArray(dbg);
 
   await previews(dbg, "empties", [
-    { line: 2, column: 9, expression: "a", result: '""' },
-    { line: 3, column: 9, expression: "b", result: "false" },
-    { line: 4, column: 9, expression: "c", result: "undefined" },
-    { line: 5, column: 9, expression: "d", result: "null" }
+    { line: 6, column: 9, expression: "a", result: '""' },
+    { line: 7, column: 9, expression: "b", result: "false" },
+    { line: 8, column: 9, expression: "c", result: "undefined" },
+    { line: 9, column: 9, expression: "d", result: "null" },
+  ]);
+
+  await previews(dbg, "objects", [
+    { line: 27, column: 10, expression: "empty", result: "No properties" },
   ]);
 
   await previews(dbg, "smalls", [
-    { line: 10, column: 9, expression: "a", result: '"..."' },
-    { line: 11, column: 9, expression: "b", result: "true" },
-    { line: 12, column: 9, expression: "c", result: "1" },
+    { line: 14, column: 9, expression: "a", result: '"..."' },
+    { line: 15, column: 9, expression: "b", result: "true" },
+    { line: 16, column: 9, expression: "c", result: "1" },
     {
-      line: 13,
+      line: 17,
       column: 9,
       expression: "d",
-      fields: [["length", "0"]]
-    }
+      fields: [["length", "0"]],
+    },
   ]);
 });
--- a/devtools/client/debugger/test/mochitest/examples/preview.js
+++ b/devtools/client/debugger/test/mochitest/examples/preview.js
@@ -1,8 +1,12 @@
+/* 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 empties() {
   const a = "";
   const b = false;
   const c = undefined;
   const d = null;
   debugger;
 }
 
@@ -12,21 +16,23 @@ function smalls() {
   const c = 1;
   const d = [];
   const e = {};
   debugger;
 }
 
 function objects() {
   const obj = {
-    foo: 1
+    foo: 1,
   };
 
+  const empty = Object.create(null);
+
   debugger;
 }
 
 function largeArray() {
-  let bs = [];
+  const bs = [];
   for (let i = 0; i <= 100; i++) {
     bs.push({ a: 2, b: { c: 3 } });
   }
   debugger;
 }
--- a/devtools/client/locales/en-US/debugger.properties
+++ b/devtools/client/locales/en-US/debugger.properties
@@ -439,16 +439,20 @@ downloadFile.accesskey=d
 # LOCALIZATION NOTE (inlinePreview.show.label): Context menu item
 # for showing the inline preview blocks
 inlinePreview.show.label=Show inline preview
 
 # LOCALIZATION NOTE (inlinePreview.hide.label): Context menu item
 # for hiding the inline preview block
 inlinePreview.hide.label=Hide inline preview
 
+# LOCALIZATION NOTE (preview.noProperties): Label shown in the preview
+# popup when there are no properties to show.
+preview.noProperties=No properties
+
 # LOCALIZATION NOTE (framework.disableGrouping): This is the text that appears in the
 # context menu to disable framework grouping.
 framework.disableGrouping=Disable framework grouping
 framework.disableGrouping.accesskey=u
 
 # LOCALIZATION NOTE (framework.enableGrouping): This is the text that appears in the
 # context menu to enable framework grouping.
 framework.enableGrouping=Enable framework grouping
--- a/devtools/client/shared/components/reps/reps.js
+++ b/devtools/client/shared/components/reps/reps.js
@@ -2733,17 +2733,17 @@ const {
 const {
   REPS: {
     Rep,
     Grip
   }
 } = __webpack_require__(24);
 
 function shouldRenderRootsInReps(roots) {
-  if (roots.length > 1) {
+  if (roots.length !== 1) {
     return false;
   }
 
   const root = roots[0];
   const name = root && root.name;
   return (name === null || typeof name === "undefined") && (nodeIsPrimitive(root) || nodeIsError(root));
 }
 
@@ -7879,16 +7879,20 @@ function mapStateToProps(state, props) {
 
 const OI = connect(mapStateToProps, actions)(ObjectInspector);
 
 module.exports = props => {
   const {
     roots
   } = props;
 
+  if (roots.length == 0) {
+    return null;
+  }
+
   if (shouldRenderRootsInReps(roots)) {
     return renderRep(roots[0], props);
   }
 
   return createElement(OI, props);
 };
 
 /***/ }),