зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1473996 - Expose getPropertyValue in devtools server to fully evaluate an object property. r=nchevobbe
Differential Revision: https://phabricator.services.mozilla.com/D6722 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
022d2224d5
Коммит
11febfa688
|
@ -507,6 +507,53 @@ const proto = {
|
|||
return { descriptor: this._propertyDescriptor(name) };
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle a protocol request to provide the value of the object's
|
||||
* specified property.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
propertyValue: function(name) {
|
||||
if (!name) {
|
||||
return this.throwError("missingParameter", "no property name was specified");
|
||||
}
|
||||
|
||||
const value = this.obj.getProperty(name);
|
||||
|
||||
return { value: this._buildCompletion(value) };
|
||||
},
|
||||
|
||||
/**
|
||||
* Converts a Debugger API completion value record into an eqivalent
|
||||
* object grip for use by the API.
|
||||
*
|
||||
* See https://developer.mozilla.org/en-US/docs/Tools/Debugger-API/Conventions#completion-values
|
||||
* for more specifics on the expected behavior.
|
||||
*/
|
||||
_buildCompletion(value) {
|
||||
let completionGrip = null;
|
||||
|
||||
// .apply result will be falsy if the script being executed is terminated
|
||||
// via the "slow script" dialog.
|
||||
if (value) {
|
||||
completionGrip = {};
|
||||
if ("return" in value) {
|
||||
completionGrip.return = this.hooks.createValueGrip(value.return);
|
||||
}
|
||||
if ("throw" in value) {
|
||||
completionGrip.throw = this.hooks.createValueGrip(value.throw);
|
||||
}
|
||||
}
|
||||
|
||||
return completionGrip;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle a protocol request to provide the display string for the object.
|
||||
*/
|
||||
|
|
|
@ -1610,7 +1610,13 @@ const ThreadActor = ActorClassWithSpec(threadSpec, {
|
|||
getGripDepth: () => this._gripDepth,
|
||||
incrementGripDepth: () => this._gripDepth++,
|
||||
decrementGripDepth: () => this._gripDepth--,
|
||||
createValueGrip: v => createValueGrip(v, this._pausePool, this.pauseObjectGrip),
|
||||
createValueGrip: v => {
|
||||
if (this._pausePool) {
|
||||
return createValueGrip(v, this._pausePool, this.pauseObjectGrip);
|
||||
}
|
||||
|
||||
return createValueGrip(v, this.threadLifetimePool, this.objectGrip);
|
||||
},
|
||||
sources: () => this.sources,
|
||||
createEnvironmentActor: (e, p) => this.createEnvironmentActor(e, p),
|
||||
promote: () => this.threadObjectGrip(actor),
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* eslint-disable no-shadow, max-nested-callbacks */
|
||||
|
||||
"use strict";
|
||||
|
||||
async function run_test() {
|
||||
try {
|
||||
do_test_pending();
|
||||
await run_test_with_server(DebuggerServer);
|
||||
await run_test_with_server(WorkerDebuggerServer);
|
||||
} finally {
|
||||
do_test_finished();
|
||||
}
|
||||
}
|
||||
|
||||
async function run_test_with_server(server) {
|
||||
initTestDebuggerServer(server);
|
||||
const debuggee = addTestGlobal("test-grips", server);
|
||||
debuggee.eval(`
|
||||
function stopMe(arg1) {
|
||||
debugger;
|
||||
}
|
||||
`);
|
||||
|
||||
const dbgClient = new DebuggerClient(server.connectPipe());
|
||||
await dbgClient.connect();
|
||||
const [,, threadClient] = await attachTestTabAndResume(dbgClient, "test-grips");
|
||||
|
||||
await test_object_grip(debuggee, threadClient);
|
||||
|
||||
await dbgClient.close();
|
||||
}
|
||||
|
||||
async function test_object_grip(debuggee, threadClient) {
|
||||
await assert_object_argument(
|
||||
debuggee,
|
||||
threadClient,
|
||||
`
|
||||
var obj = {
|
||||
stringProp: "a value",
|
||||
get stringNormal(){
|
||||
return "a value";
|
||||
},
|
||||
get stringAbrupt() {
|
||||
throw "a value";
|
||||
},
|
||||
get objectNormal() {
|
||||
return { prop: 4 };
|
||||
},
|
||||
get objectAbrupt() {
|
||||
throw { prop: 4 };
|
||||
},
|
||||
get context(){
|
||||
return this === obj ? "correct context" : "wrong context";
|
||||
},
|
||||
method() {
|
||||
return "a value";
|
||||
},
|
||||
};
|
||||
stopMe(obj);
|
||||
`,
|
||||
async objClient => {
|
||||
const expectedValues = {
|
||||
stringProp: {
|
||||
return: "a value",
|
||||
},
|
||||
stringNormal: {
|
||||
return: "a value",
|
||||
},
|
||||
stringAbrupt: {
|
||||
throw: "a value",
|
||||
},
|
||||
objectNormal: {
|
||||
return: {
|
||||
type: "object",
|
||||
class: "Object",
|
||||
ownPropertyLength: 1,
|
||||
preview: {
|
||||
kind: "Object",
|
||||
ownProperties: {
|
||||
prop: {
|
||||
value: 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
objectAbrupt: {
|
||||
throw: {
|
||||
type: "object",
|
||||
class: "Object",
|
||||
ownPropertyLength: 1,
|
||||
preview: {
|
||||
kind: "Object",
|
||||
ownProperties: {
|
||||
prop: {
|
||||
value: 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
context: {
|
||||
return: "correct context",
|
||||
},
|
||||
method: {
|
||||
return: {
|
||||
type: "object",
|
||||
class: "Function",
|
||||
name: "method",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
for (const [key, expected] of Object.entries(expectedValues)) {
|
||||
const { value } = await objClient.getPropertyValue(key);
|
||||
|
||||
assert_completion(value, expected);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function assert_object_argument(debuggee, threadClient, code, objectHandler) {
|
||||
return eval_and_resume(debuggee, threadClient, code, async frame => {
|
||||
const arg1 = frame.arguments[0];
|
||||
Assert.equal(arg1.class, "Object");
|
||||
|
||||
await objectHandler(threadClient.pauseGrip(arg1));
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function assert_completion(value, expected) {
|
||||
if (expected && "return" in expected) {
|
||||
assert_value(value.return, expected.return);
|
||||
}
|
||||
if (expected && "throw" in expected) {
|
||||
assert_value(value.throw, expected.throw);
|
||||
}
|
||||
if (!expected) {
|
||||
assert_value(value, expected);
|
||||
}
|
||||
}
|
||||
|
||||
function assert_value(actual, expected) {
|
||||
Assert.equal(typeof actual, typeof expected);
|
||||
|
||||
if (typeof expected === "object") {
|
||||
// Note: We aren't using deepEqual here because we're only doing a cursory
|
||||
// check of a few properties, not a full comparison of the result, since
|
||||
// the full outputs includes stuff like preview info that we don't need.
|
||||
for (const key of Object.keys(expected)) {
|
||||
assert_value(actual[key], expected[key]);
|
||||
}
|
||||
} else {
|
||||
Assert.equal(actual, expected);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
/* eslint-disable no-shadow, max-nested-callbacks */
|
||||
|
||||
"use strict";
|
||||
|
||||
async function run_test() {
|
||||
try {
|
||||
do_test_pending();
|
||||
await run_test_with_server(DebuggerServer);
|
||||
await run_test_with_server(WorkerDebuggerServer);
|
||||
} finally {
|
||||
do_test_finished();
|
||||
}
|
||||
}
|
||||
|
||||
async function run_test_with_server(server) {
|
||||
initTestDebuggerServer(server);
|
||||
const debuggee = addTestGlobal("test-grips", server);
|
||||
debuggee.eval(`
|
||||
function stopMe(arg1) {
|
||||
debugger;
|
||||
}
|
||||
`);
|
||||
|
||||
const dbgClient = new DebuggerClient(server.connectPipe());
|
||||
await dbgClient.connect();
|
||||
const [,, threadClient] = await attachTestTabAndResume(dbgClient, "test-grips");
|
||||
|
||||
await test_object_grip(debuggee, threadClient);
|
||||
|
||||
await dbgClient.close();
|
||||
}
|
||||
|
||||
async function test_object_grip(debuggee, threadClient) {
|
||||
const code = `
|
||||
stopMe({
|
||||
get prop(){
|
||||
debugger;
|
||||
},
|
||||
});
|
||||
`;
|
||||
const objClient = await eval_and_resume(debuggee, threadClient, code, async frame => {
|
||||
const arg1 = frame.arguments[0];
|
||||
Assert.equal(arg1.class, "Object");
|
||||
|
||||
const obj = threadClient.pauseGrip(arg1);
|
||||
await obj.threadGrip();
|
||||
return obj;
|
||||
});
|
||||
|
||||
// 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"),
|
||||
]);
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -177,6 +177,8 @@ reason = bug 1104838
|
|||
[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_promise_state-01.js]
|
||||
[test_promise_state-02.js]
|
||||
[test_promise_state-03.js]
|
||||
|
|
|
@ -42,6 +42,10 @@ ObjectClient.prototype = {
|
|||
return this._grip.extensible;
|
||||
},
|
||||
|
||||
threadGrip: DebuggerClient.requester({
|
||||
type: "threadGrip",
|
||||
}),
|
||||
|
||||
getDefinitionSite: DebuggerClient.requester({
|
||||
type: "definitionSite"
|
||||
}, {
|
||||
|
@ -181,6 +185,17 @@ ObjectClient.prototype = {
|
|||
name: arg(0)
|
||||
}),
|
||||
|
||||
/**
|
||||
* Request the value of the object's specified property.
|
||||
*
|
||||
* @param name string The name of the requested property.
|
||||
* @param onResponse function Called with the request's response.
|
||||
*/
|
||||
getPropertyValue: DebuggerClient.requester({
|
||||
type: "propertyValue",
|
||||
name: arg(0)
|
||||
}),
|
||||
|
||||
/**
|
||||
* Request the prototype of the object.
|
||||
*
|
||||
|
|
|
@ -24,6 +24,11 @@ types.addDictType("object.descriptor", {
|
|||
set: "nullable:json",
|
||||
});
|
||||
|
||||
types.addDictType("object.completion", {
|
||||
return: "nullable:json",
|
||||
throw: "nullable:json"
|
||||
});
|
||||
|
||||
types.addDictType("object.definitionSite", {
|
||||
source: "source",
|
||||
line: "number",
|
||||
|
@ -45,6 +50,10 @@ types.addDictType("object.property", {
|
|||
descriptor: "nullable:object.descriptor"
|
||||
});
|
||||
|
||||
types.addDictType("object.propertyValue", {
|
||||
value: "nullable:object.completion"
|
||||
});
|
||||
|
||||
types.addDictType("object.bindings", {
|
||||
arguments: "array:json",
|
||||
variables: "json",
|
||||
|
@ -165,6 +174,12 @@ const objectSpec = generateActorSpec({
|
|||
},
|
||||
response: RetVal("object.property")
|
||||
},
|
||||
propertyValue: {
|
||||
request: {
|
||||
name: Arg(0, "string")
|
||||
},
|
||||
response: RetVal("object.propertyValue")
|
||||
},
|
||||
rejectionStack: {
|
||||
request: {},
|
||||
response: {
|
||||
|
|
Загрузка…
Ссылка в новой задаче