Bug 1515046 - Let reps invoke the right getter when it's shadowed. r=nchevobbe, a=RyanVM
authorOriol Brufau <oriol-bugzilla@hotmail.com>
Sat, 12 Jan 2019 18:33:55 -0500
changeset 509436 45793c591e2d44e512cca27b6573fb38022fab88
parent 509435 d5ed432f8da4733c3ab2b8f4ed0a139a4b637d6d
child 509437 6ca5c3a1e5b4e21cc4677288354c00072156be2a
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnchevobbe, RyanVM
bugs1515046
milestone65.0
Bug 1515046 - Let reps invoke the right getter when it's shadowed. r=nchevobbe, a=RyanVM Depends on D15788 Differential Revision: https://phabricator.services.mozilla.com/D15789
devtools/client/shared/components/reps/reps.js
devtools/client/webconsole/test/mochitest/browser.ini
devtools/client/webconsole/test/mochitest/browser_webconsole_object_inspector_getters_shadowed.js
--- a/devtools/client/shared/components/reps/reps.js
+++ b/devtools/client/shared/components/reps/reps.js
@@ -2379,23 +2379,36 @@ function getClosestNonBucketNode(item) {
   const parent = getParent(item);
   if (!parent) {
     return null;
   }
 
   return getClosestNonBucketNode(parent);
 }
 
-function getNonPrototypeParentGripValue(item) {
+function getParentGripNode(item) {
   const parentNode = getParent(item);
   if (!parentNode) {
     return null;
   }
 
-  const parentGripNode = getClosestGripNode(parentNode);
+  return getClosestGripNode(parentNode);
+}
+
+function getParentGripValue(item) {
+  const parentGripNode = getParentGripNode(item);
+  if (!parentGripNode) {
+    return null;
+  }
+
+  return getValue(parentGripNode);
+}
+
+function getNonPrototypeParentGripValue(item) {
+  const parentGripNode = getParentGripNode(item);
   if (!parentGripNode) {
     return null;
   }
 
   if (getType(parentGripNode) === NODE_TYPES.PROTOTYPE) {
     return getNonPrototypeParentGripValue(parentGripNode);
   }
 
@@ -2407,16 +2420,17 @@ module.exports = {
   createGetterNode,
   createSetterNode,
   getActor,
   getChildren,
   getChildrenWithEvaluations,
   getClosestGripNode,
   getClosestNonBucketNode,
   getParent,
+  getParentGripValue,
   getNonPrototypeParentGripValue,
   getNumericalPropertiesCount,
   getValue,
   makeNodesForEntries,
   makeNodesForPromiseProperties,
   makeNodesForProperties,
   makeNumericalBuckets,
   nodeHasAccessors,
@@ -6865,21 +6879,21 @@ function rootsChanged(props) {
 
 function releaseActors(state, client) {
   const actors = getActors(state);
   for (const actor of actors) {
     client.releaseActor(actor);
   }
 }
 
-function invokeGetter(node, grip, getterName) {
+function invokeGetter(node, targetGrip, receiverId, getterName) {
   return async ({ dispatch, client, getState }) => {
     try {
-      const objectClient = client.createObjectClient(grip);
-      const result = await objectClient.getPropertyValue(getterName, null);
+      const objectClient = client.createObjectClient(targetGrip);
+      const result = await objectClient.getPropertyValue(getterName, receiverId);
       dispatch({
         type: "GETTER_INVOKED",
         data: {
           node,
           result
         }
       });
     } catch (e) {
@@ -6950,16 +6964,17 @@ const {
   nodeIsSetter,
   nodeIsUninitializedBinding,
   nodeIsUnmappedBinding,
   nodeIsUnscopedBinding,
   nodeIsWindow,
   nodeIsLongString,
   nodeHasFullText,
   nodeHasGetter,
+  getParentGripValue,
   getNonPrototypeParentGripValue
 } = Utils.node;
 
 class ObjectInspectorItem extends Component {
   // eslint-disable-next-line complexity
   getLabelAndValue() {
     const { item, depth, expanded, mode } = this.props;
 
@@ -7024,20 +7039,21 @@ class ObjectInspectorItem extends Compon
 
       if (nodeIsLongString(item)) {
         repProps.member = {
           open: nodeHasFullText(item) && expanded
         };
       }
 
       if (nodeHasGetter(item)) {
-        const parentGrip = getNonPrototypeParentGripValue(item);
-        if (parentGrip) {
+        const targetGrip = getParentGripValue(item);
+        const receiverGrip = getNonPrototypeParentGripValue(item);
+        if (targetGrip && receiverGrip) {
           Object.assign(repProps, {
-            onInvokeGetterButtonClick: () => this.props.invokeGetter(item, parentGrip, item.name)
+            onInvokeGetterButtonClick: () => this.props.invokeGetter(item, targetGrip, receiverGrip.actor, item.name)
           });
         }
       }
 
       return {
         label,
         value: Utils.renderRep(item, repProps)
       };
--- a/devtools/client/webconsole/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/test/mochitest/browser.ini
@@ -342,16 +342,17 @@ skip-if = true  # Bug 1438979
 [browser_webconsole_nodes_select.js]
 [browser_webconsole_object_ctrl_click.js]
 [browser_webconsole_object_in_sidebar_keyboard_nav.js]
 [browser_webconsole_object_inspector.js]
 [browser_webconsole_object_inspector__proto__.js]
 [browser_webconsole_object_inspector_entries.js]
 [browser_webconsole_object_inspector_getters.js]
 [browser_webconsole_object_inspector_getters_prototype.js]
+[browser_webconsole_object_inspector_getters_shadowed.js]
 [browser_webconsole_object_inspector_key_sorting.js]
 [browser_webconsole_object_inspector_local_session_storage.js]
 [browser_webconsole_object_inspector_selected_text.js]
 [browser_webconsole_object_inspector_scroll.js]
 [browser_webconsole_object_inspector_while_debugging_and_inspecting.js]
 [browser_webconsole_observer_notifications.js]
 [browser_webconsole_optimized_out_vars.js]
 [browser_webconsole_output_copy.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_object_inspector_getters_shadowed.js
@@ -0,0 +1,79 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Check evaluating shadowed getters in the console.
+const TEST_URI = "data:text/html;charset=utf8,<h1>Object Inspector on Getters</h1>";
+
+add_task(async function() {
+  const hud = await openNewTabAndConsole(TEST_URI);
+
+  await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
+    const a = {
+      getter: "[A]",
+      __proto__: {
+        get getter() {
+          return "[B]";
+        },
+        __proto__: {
+          get getter() {
+            return "[C]";
+          },
+        },
+      },
+    };
+    const b = {
+      value: 1,
+      get getter() {
+        return `[A-${this.value}]`;
+      },
+      __proto__: {
+        value: 2,
+        get getter() {
+          return `[B-${this.value}]`;
+        },
+      },
+    };
+    content.wrappedJSObject.console.log("oi-test", a, b);
+  });
+
+  const node = await waitFor(() => findMessage(hud, "oi-test"));
+  const [a, b] = node.querySelectorAll(".tree");
+
+  await testObject(a, [null, "[B]", "[C]"]);
+  await testObject(b, ["[A-1]", "[B-1]"]);
+});
+
+async function testObject(oi, values) {
+  let node = oi.querySelector(".tree-node");
+  for (const value of values) {
+    await expand(node);
+    if (value != null) {
+      const getter = findObjectInspectorNodeChild(node, "getter");
+      await invokeGetter(getter);
+      ok(getter.textContent.includes(`getter: "${value}"`),
+        `Getter now has the expected "${value}" content`);
+    }
+    node = findObjectInspectorNodeChild(node, "<prototype>");
+  }
+}
+
+function expand(node) {
+  expandObjectInspectorNode(node);
+  return waitFor(() => getObjectInspectorChildrenNodes(node).length > 0);
+}
+
+function invokeGetter(node) {
+  getObjectInspectorInvokeGetterButton(node).click();
+  return waitFor(() => !getObjectInspectorInvokeGetterButton(node));
+}
+
+function findObjectInspectorNodeChild(node, nodeLabel) {
+  return getObjectInspectorChildrenNodes(node).find(child => {
+    const label = child.querySelector(".object-label");
+    return label && label.textContent === nodeLabel;
+  });
+}