Bug 1515046 - Add receiverId parameter in objectClient.getPropertyValue. r=nchevobbe, a=RyanVM
authorOriol Brufau <oriol-bugzilla@hotmail.com>
Sat, 12 Jan 2019 18:32:55 -0500
changeset 509435 d5ed432f8da4733c3ab2b8f4ed0a139a4b637d6d
parent 509434 7bfd0ddce88e35eb5be95af2f62d7a50208b0741
child 509436 45793c591e2d44e512cca27b6573fb38022fab88
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 - Add receiverId parameter in objectClient.getPropertyValue. r=nchevobbe, a=RyanVM Differential Revision: https://phabricator.services.mozilla.com/D15788
devtools/client/shared/components/reps/reps.js
devtools/server/actors/object.js
devtools/server/tests/unit/test_objectgrips-fn-apply-01.js
devtools/server/tests/unit/test_objectgrips-fn-apply-02.js
devtools/server/tests/unit/test_objectgrips-fn-apply-03.js
devtools/server/tests/unit/test_objectgrips-property-value-01.js
devtools/server/tests/unit/test_objectgrips-property-value-02.js
devtools/server/tests/unit/test_objectgrips-property-value-03.js
devtools/server/tests/unit/xpcshell.ini
devtools/shared/client/object-client.js
devtools/shared/specs/object.js
--- a/devtools/client/shared/components/reps/reps.js
+++ b/devtools/client/shared/components/reps/reps.js
@@ -6869,17 +6869,17 @@ function releaseActors(state, client) {
     client.releaseActor(actor);
   }
 }
 
 function invokeGetter(node, grip, getterName) {
   return async ({ dispatch, client, getState }) => {
     try {
       const objectClient = client.createObjectClient(grip);
-      const result = await objectClient.getPropertyValue(getterName);
+      const result = await objectClient.getPropertyValue(getterName, null);
       dispatch({
         type: "GETTER_INVOKED",
         data: {
           node,
           result
         }
       });
     } catch (e) {
--- a/devtools/server/actors/object.js
+++ b/devtools/server/actors/object.js
@@ -513,23 +513,36 @@ const proto = {
    *
    * Note: Since this will evaluate getters, it can trigger execution of
    * content code and may cause side effects. This endpoint should only be used
    * when you are confident that the side-effects will be safe, or the user
    * is expecting the effects.
    *
    * @param {string} name
    *        The property we want the value of.
+   * @param {string|null} receiverId
+   *        The actorId of the receiver to be used if the property is a getter.
+   *        If null or invalid, the receiver will be the referent.
    */
-  propertyValue: function(name) {
+  propertyValue: function(name, receiverId) {
     if (!name) {
       return this.throwError("missingParameter", "no property name was specified");
     }
 
-    const value = this.obj.getProperty(name);
+    let receiver;
+    if (receiverId) {
+      const receiverActor = this.conn.getActor(receiverId);
+      if (receiverActor) {
+        receiver = receiverActor.obj;
+      }
+    }
+
+    const value = receiver
+      ? this.obj.getProperty(name, receiver)
+      : this.obj.getProperty(name);
 
     return { value: this._buildCompletion(value) };
   },
 
   /**
    * Handle a protocol request to evaluate a function and provide the value of
    * the result.
    *
--- a/devtools/server/tests/unit/test_objectgrips-fn-apply-01.js
+++ b/devtools/server/tests/unit/test_objectgrips-fn-apply-01.js
@@ -32,27 +32,27 @@ async function test_object_grip(debuggee
           return parts.reduce((acc, v) => acc + v, 0);
         },
         error() {
           throw "an error";
         },
       });
     `,
     async objClient => {
-      const obj1 = (await objClient.getPropertyValue("obj1")).value.return;
-      const obj2 = (await objClient.getPropertyValue("obj2")).value.return;
+      const obj1 = (await objClient.getPropertyValue("obj1", null)).value.return;
+      const obj2 = (await objClient.getPropertyValue("obj2", null)).value.return;
 
       const context = threadClient.pauseGrip(
-        (await objClient.getPropertyValue("context")).value.return,
+        (await objClient.getPropertyValue("context", null)).value.return,
       );
       const sum = threadClient.pauseGrip(
-        (await objClient.getPropertyValue("sum")).value.return,
+        (await objClient.getPropertyValue("sum", null)).value.return,
       );
       const error = threadClient.pauseGrip(
-        (await objClient.getPropertyValue("error")).value.return,
+        (await objClient.getPropertyValue("error", null)).value.return,
       );
 
       assert_response(await context.apply(obj1, [obj1]), {
         return: "correct context",
       });
       assert_response(await context.apply(obj2, [obj2]), {
         return: "correct context",
       });
--- a/devtools/server/tests/unit/test_objectgrips-fn-apply-02.js
+++ b/devtools/server/tests/unit/test_objectgrips-fn-apply-02.js
@@ -30,17 +30,17 @@ async function test_object_grip(debuggee
     Assert.equal(arg1.class, "Object");
 
     await threadClient.pauseGrip(arg1).threadGrip();
     return arg1;
   });
   const objClient = threadClient.pauseGrip(obj);
 
   const method = threadClient.pauseGrip(
-    (await objClient.getPropertyValue("method")).value.return,
+    (await objClient.getPropertyValue("method", null)).value.return,
   );
 
   // Ensure that we actually paused at the `debugger;` line.
   await Promise.all([
     wait_for_pause(threadClient, frame => {
       Assert.equal(frame.where.line, 4);
       Assert.equal(frame.where.column, 8);
     }),
--- a/devtools/server/tests/unit/test_objectgrips-fn-apply-03.js
+++ b/devtools/server/tests/unit/test_objectgrips-fn-apply-03.js
@@ -28,17 +28,17 @@ async function test_object_grip(debuggee
     Assert.equal(arg1.class, "Object");
 
     await threadClient.pauseGrip(arg1).threadGrip();
     return arg1;
   });
   const objClient = threadClient.pauseGrip(obj);
 
   const method = threadClient.pauseGrip(
-    (await objClient.getPropertyValue("method")).value.return,
+    (await objClient.getPropertyValue("method", null)).value.return,
   );
 
   try {
     await method.apply(obj, []);
     Assert.ok(false, "expected exception");
   } catch (err) {
     Assert.equal(err.message, "debugee object is not callable");
   }
--- a/devtools/server/tests/unit/test_objectgrips-property-value-01.js
+++ b/devtools/server/tests/unit/test_objectgrips-property-value-01.js
@@ -94,17 +94,17 @@ async function test_object_grip(debuggee
             type: "object",
             class: "Function",
             name: "method",
           },
         },
       };
 
       for (const [key, expected] of Object.entries(expectedValues)) {
-        const { value } = await objClient.getPropertyValue(key);
+        const { value } = await objClient.getPropertyValue(key, null);
 
         assert_completion(value, expected);
       }
     },
   );
 }
 
 function assert_object_argument(debuggee, threadClient, code, objectHandler) {
--- a/devtools/server/tests/unit/test_objectgrips-property-value-02.js
+++ b/devtools/server/tests/unit/test_objectgrips-property-value-02.js
@@ -35,17 +35,17 @@ async function test_object_grip(debuggee
   });
 
   // Ensure that we actually paused at the `debugger;` line.
   await Promise.all([
     wait_for_pause(threadClient, frame => {
       Assert.equal(frame.where.line, 4);
       Assert.equal(frame.where.column, 8);
     }),
-    objClient.getPropertyValue("prop"),
+    objClient.getPropertyValue("prop", null),
   ]);
 }
 
 function eval_and_resume(debuggee, threadClient, code, callback) {
   return new Promise((resolve, reject) => {
     wait_for_pause(threadClient, callback).then(resolve, reject);
 
     // This synchronously blocks until 'threadClient.resume()' above runs
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/unit/test_objectgrips-property-value-03.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+/* eslint-disable no-shadow, max-nested-callbacks */
+
+"use strict";
+
+Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true);
+registerCleanupFunction(() => {
+  Services.prefs.clearUserPref("security.allow_eval_with_system_principal");
+});
+
+add_task(threadClientTest(async ({ threadClient, debuggee, client }) => {
+  debuggee.eval(function stopMe() {
+    debugger;
+  }.toString());
+
+  await test_object_grip(debuggee, threadClient);
+}));
+
+async function test_object_grip(debuggee, threadClient) {
+  eval_and_resume(
+    debuggee,
+    threadClient,
+    `
+      var obj = {
+        get getter() {
+          return objects.indexOf(this);
+        },
+      };
+      var objects = [obj, {}, [], new Boolean(), new Number(), new String()];
+      stopMe(...objects);
+    `,
+    async frame => {
+      const grips = frame.arguments;
+      const objClient = threadClient.pauseGrip(grips[0]);
+      const classes = ["Object", "Object", "Array", "Boolean", "Number", "String"];
+      for (const [i, grip] of grips.entries()) {
+        Assert.equal(grip.class, classes[i]);
+        await check_getter(objClient, grip.actor, i);
+      }
+      await check_getter(objClient, null, 0);
+      await check_getter(objClient, "invalid receiver actorId", 0);
+    }
+  );
+}
+
+function eval_and_resume(debuggee, threadClient, code, callback) {
+  return new Promise((resolve, reject) => {
+    wait_for_pause(threadClient, callback).then(resolve, reject);
+
+    // This synchronously blocks until 'threadClient.resume()' above runs
+    // because the 'paused' event runs everthing in a new event loop.
+    debuggee.eval(code);
+  });
+}
+
+function wait_for_pause(threadClient, callback = () => {}) {
+  return new Promise((resolve, reject) => {
+    threadClient.addOneTimeListener("paused", function(event, packet) {
+      (async () => {
+        try {
+          return await callback(packet.frame);
+        } finally {
+          await threadClient.resume();
+        }
+      })().then(resolve, reject);
+    });
+  });
+}
+
+async function check_getter(objClient, receiverId, expected) {
+  const {value} = await objClient.getPropertyValue("getter", receiverId);
+  Assert.equal(value.return, expected);
+}
--- a/devtools/server/tests/unit/xpcshell.ini
+++ b/devtools/server/tests/unit/xpcshell.ini
@@ -178,16 +178,17 @@ reason = bug 1104838
 [test_objectgrips-18.js]
 [test_objectgrips-19.js]
 [test_objectgrips-20.js]
 [test_objectgrips-21.js]
 [test_objectgrips-22.js]
 [test_objectgrips-array-like-object.js]
 [test_objectgrips-property-value-01.js]
 [test_objectgrips-property-value-02.js]
+[test_objectgrips-property-value-03.js]
 [test_objectgrips-fn-apply-01.js]
 [test_objectgrips-fn-apply-02.js]
 [test_objectgrips-fn-apply-03.js]
 [test_promise_state-01.js]
 [test_promise_state-02.js]
 [test_promise_state-03.js]
 [test_interrupt.js]
 [test_stepping-01.js]
--- a/devtools/shared/client/object-client.js
+++ b/devtools/shared/client/object-client.js
@@ -184,21 +184,23 @@ ObjectClient.prototype = {
     type: "property",
     name: arg(0),
   }),
 
   /**
    * Request the value of the object's specified property.
    *
    * @param name string The name of the requested property.
+   * @param receiverId string|null The actorId of the receiver to be used for getters.
    * @param onResponse function Called with the request's response.
    */
   getPropertyValue: DebuggerClient.requester({
     type: "propertyValue",
     name: arg(0),
+    receiverId: arg(1),
   }),
 
   /**
    * Request the prototype of the object.
    *
    * @param onResponse function Called with the request's response.
    */
   getPrototype: DebuggerClient.requester({
--- a/devtools/shared/specs/object.js
+++ b/devtools/shared/specs/object.js
@@ -176,16 +176,17 @@ const objectSpec = generateActorSpec({
       request: {
         name: Arg(0, "string"),
       },
       response: RetVal("object.property"),
     },
     propertyValue: {
       request: {
         name: Arg(0, "string"),
+        receiverId: Arg(1, "nullable:string"),
       },
       response: RetVal("object.propertyValue"),
     },
     apply: {
       request: {
         context: Arg(0, "nullable:json"),
         arguments: Arg(1, "nullable:array:json"),
       },