Bug 1515046 - Let reps invoke the right getter when it's shadowed. r=nchevobbe
authorOriol Brufau <oriol-bugzilla@hotmail.com>
Wed, 09 Jan 2019 01:47:34 +0000
changeset 510335 524ec48474fdb5fad28a0e3e393b3762657ab98b
parent 510334 874c30e2b9346d1b8f73b207894fe5ea008065c9
child 510336 876faba9c48f5bee522dae19d91acde4b60efc86
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnchevobbe
bugs1515046
milestone66.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 1515046 - Let reps invoke the right getter when it's shadowed. r=nchevobbe 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
@@ -3683,23 +3683,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);
   }
 
@@ -3711,16 +3724,17 @@ module.exports = {
   createGetterNode,
   createSetterNode,
   getActor,
   getChildren,
   getChildrenWithEvaluations,
   getClosestGripNode,
   getClosestNonBucketNode,
   getParent,
+  getParentGripValue,
   getNonPrototypeParentGripValue,
   getNumericalPropertiesCount,
   getValue,
   makeNodesForEntries,
   makeNodesForPromiseProperties,
   makeNodesForProperties,
   makeNumericalBuckets,
   nodeHasAccessors,
@@ -6401,21 +6415,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) {
@@ -6999,16 +7013,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;
 
@@ -7073,20 +7088,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
@@ -350,16 +350,17 @@ skip-if = true  # Bug 1438979
 [browser_webconsole_non_javascript_mime_warning.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;
+  });
+}