Bug 1515046 - Add receiverId parameter in objectClient.getPropertyValue. r=nchevobbe
authorOriol Brufau <oriol-bugzilla@hotmail.com>
Wed, 09 Jan 2019 06:42:58 +0000
changeset 453231 874c30e2b9346d1b8f73b207894fe5ea008065c9
parent 453230 ecb0589b10796f3e40d33108852313ef90546c42
child 453232 524ec48474fdb5fad28a0e3e393b3762657ab98b
push id35349
push userbtara@mozilla.com
push dateThu, 10 Jan 2019 17:19:27 +0000
treeherdermozilla-central@a51746f37520 [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 - Add receiverId parameter in objectClient.getPropertyValue. r=nchevobbe 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
@@ -6405,17 +6405,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
@@ -506,23 +506,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
@@ -160,16 +160,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"),
       },