mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 21:31:04 +00:00
Bug 1392760 - Avoid exponential behavior when inspecting nested proxies. r=nchevobbe
Differential Revision: https://phabricator.services.mozilla.com/D28214 --HG-- extra : moz-landing-system : lando
This commit is contained in:
parent
d7daa3c130
commit
4be85f2295
@ -54,7 +54,8 @@ function mount(props, { initialState } = {}) {
|
||||
const client = {
|
||||
createObjectClient: grip =>
|
||||
ObjectClient(grip, {
|
||||
getPrototype: () => Promise.resolve(protoStub)
|
||||
getPrototype: () => Promise.resolve(protoStub),
|
||||
getProxySlots: () => Promise.resolve(gripRepStubs.get("testProxySlots"))
|
||||
}),
|
||||
|
||||
createLongStringClient: grip =>
|
||||
|
@ -6,7 +6,9 @@
|
||||
const { mountObjectInspector } = require("../test-utils");
|
||||
|
||||
const { MODE } = require("../../../reps/constants");
|
||||
const stub = require("../../../reps/stubs/grip").get("testProxy");
|
||||
const gripStubs = require("../../../reps/stubs/grip");
|
||||
const stub = gripStubs.get("testProxy");
|
||||
const proxySlots = gripStubs.get("testProxySlots");
|
||||
const { formatObjectInspector } = require("../test-utils");
|
||||
|
||||
const ObjectClient = require("../__mocks__/object-client");
|
||||
@ -32,11 +34,17 @@ function getEnumPropertiesMock() {
|
||||
}));
|
||||
}
|
||||
|
||||
function getProxySlotsMock() {
|
||||
return jest.fn(() => proxySlots);
|
||||
}
|
||||
|
||||
function mount(props, { initialState } = {}) {
|
||||
const enumProperties = getEnumPropertiesMock();
|
||||
const getProxySlots = getProxySlotsMock();
|
||||
|
||||
const client = {
|
||||
createObjectClient: grip => ObjectClient(grip, { enumProperties })
|
||||
createObjectClient: grip =>
|
||||
ObjectClient(grip, { enumProperties, getProxySlots })
|
||||
};
|
||||
|
||||
const obj = mountObjectInspector({
|
||||
@ -45,19 +53,19 @@ function mount(props, { initialState } = {}) {
|
||||
initialState
|
||||
});
|
||||
|
||||
return { ...obj, enumProperties };
|
||||
return { ...obj, enumProperties, getProxySlots };
|
||||
}
|
||||
|
||||
describe("ObjectInspector - Proxy", () => {
|
||||
it("renders Proxy as expected", () => {
|
||||
const { wrapper, enumProperties } = mount(
|
||||
const { wrapper, enumProperties, getProxySlots } = mount(
|
||||
{},
|
||||
{
|
||||
initialState: {
|
||||
objectInspector: {
|
||||
// Have the prototype already loaded so the component does not call
|
||||
// enumProperties for the root's properties.
|
||||
loadedProperties: new Map([["root", { prototype: {} }]]),
|
||||
loadedProperties: new Map([["root", proxySlots]]),
|
||||
evaluations: new Map()
|
||||
}
|
||||
}
|
||||
@ -68,6 +76,9 @@ describe("ObjectInspector - Proxy", () => {
|
||||
|
||||
// enumProperties should not have been called.
|
||||
expect(enumProperties.mock.calls).toHaveLength(0);
|
||||
|
||||
// getProxySlots should not have been called.
|
||||
expect(getProxySlots.mock.calls).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("calls enumProperties on <target> and <handler> clicks", () => {
|
||||
@ -78,7 +89,7 @@ describe("ObjectInspector - Proxy", () => {
|
||||
objectInspector: {
|
||||
// Have the prototype already loaded so the component does not call
|
||||
// enumProperties for the root's properties.
|
||||
loadedProperties: new Map([["root", { prototype: {} }]]),
|
||||
loadedProperties: new Map([["root", proxySlots]]),
|
||||
evaluations: new Map()
|
||||
}
|
||||
}
|
||||
|
@ -75,14 +75,15 @@ describe("getChildren", () => {
|
||||
});
|
||||
|
||||
it("returns the expected nodes for Proxy", () => {
|
||||
const nodes = getChildren({
|
||||
item: createNode({
|
||||
const proxyNode = createNode({
|
||||
name: "root",
|
||||
path: "rootpath",
|
||||
contents: { value: gripStubs.get("testProxy") }
|
||||
})
|
||||
});
|
||||
|
||||
const loadedProperties = new Map([
|
||||
[proxyNode.path, gripStubs.get("testProxySlots")]
|
||||
]);
|
||||
const nodes = getChildren({ item: proxyNode, loadedProperties });
|
||||
const names = nodes.map(n => n.name);
|
||||
const paths = nodes.map(n => n.path.toString());
|
||||
|
||||
@ -248,7 +249,7 @@ describe("getChildren", () => {
|
||||
const children = getChildren({
|
||||
cachedNodes,
|
||||
item: node,
|
||||
loadedProperties: new Map([[node.path, { prototype: {} }]])
|
||||
loadedProperties: new Map([[node.path, gripStubs.get("testProxySlots")]])
|
||||
});
|
||||
expect(cachedNodes.get(node.path)).toBe(children);
|
||||
});
|
||||
|
@ -210,7 +210,10 @@ describe("shouldLoadItemIndexedProperties", () => {
|
||||
value: gripStubs.get("testProxy")
|
||||
}
|
||||
});
|
||||
const [targetNode] = getChildren({ item: proxyNode });
|
||||
const loadedProperties = new Map([
|
||||
[proxyNode.path, gripStubs.get("testProxySlots")]
|
||||
]);
|
||||
const [targetNode] = getChildren({ item: proxyNode, loadedProperties });
|
||||
// Make sure we have the target node.
|
||||
expect(targetNode.name).toBe("<target>");
|
||||
expect(shouldLoadItemIndexedProperties(targetNode)).toBeTruthy();
|
||||
|
@ -173,7 +173,10 @@ describe("shouldLoadItemNonIndexedProperties", () => {
|
||||
value: gripStubs.get("testProxy")
|
||||
}
|
||||
});
|
||||
const [targetNode] = getChildren({ item: proxyNode });
|
||||
const loadedProperties = new Map([
|
||||
[proxyNode.path, gripStubs.get("testProxySlots")]
|
||||
]);
|
||||
const [targetNode] = getChildren({ item: proxyNode, loadedProperties });
|
||||
// Make sure we have the target node.
|
||||
expect(targetNode.name).toBe("<target>");
|
||||
expect(shouldLoadItemNonIndexedProperties(targetNode)).toBeTruthy();
|
||||
|
@ -152,14 +152,14 @@ describe("shouldLoadItemPrototype", () => {
|
||||
expect(shouldLoadItemPrototype(node)).toBeFalsy();
|
||||
});
|
||||
|
||||
it("returns true for a Proxy node", () => {
|
||||
it("returns false for a Proxy node", () => {
|
||||
const node = createNode({
|
||||
name: "root",
|
||||
contents: {
|
||||
value: gripStubs.get("testProxy")
|
||||
}
|
||||
});
|
||||
expect(shouldLoadItemPrototype(node)).toBeTruthy();
|
||||
expect(shouldLoadItemPrototype(node)).toBeFalsy();
|
||||
});
|
||||
|
||||
it("returns true for a Proxy target node", () => {
|
||||
@ -169,7 +169,10 @@ describe("shouldLoadItemPrototype", () => {
|
||||
value: gripStubs.get("testProxy")
|
||||
}
|
||||
});
|
||||
const [targetNode] = getChildren({ item: proxyNode });
|
||||
const loadedProperties = new Map([
|
||||
[proxyNode.path, gripStubs.get("testProxySlots")]
|
||||
]);
|
||||
const [targetNode] = getChildren({ item: proxyNode, loadedProperties });
|
||||
// Make sure we have the target node.
|
||||
expect(targetNode.name).toBe("<target>");
|
||||
expect(shouldLoadItemPrototype(targetNode)).toBeTruthy();
|
||||
|
@ -169,7 +169,10 @@ describe("shouldLoadItemSymbols", () => {
|
||||
value: gripStubs.get("testProxy")
|
||||
}
|
||||
});
|
||||
const [targetNode] = getChildren({ item: proxyNode });
|
||||
const loadedProperties = new Map([
|
||||
[proxyNode.path, gripStubs.get("testProxySlots")]
|
||||
]);
|
||||
const [targetNode] = getChildren({ item: proxyNode, loadedProperties });
|
||||
// Make sure we have the target node.
|
||||
expect(targetNode.name).toBe("<target>");
|
||||
expect(shouldLoadItemSymbols(targetNode)).toBeTruthy();
|
||||
|
@ -73,7 +73,8 @@ export type ObjectClient = {
|
||||
enumEntries: () => Promise<PropertiesIterator>,
|
||||
enumProperties: (options: Object) => Promise<PropertiesIterator>,
|
||||
enumSymbols: () => Promise<PropertiesIterator>,
|
||||
getPrototype: () => Promise<{ prototype: Object }>
|
||||
getPrototype: () => Promise<{ prototype: Object }>,
|
||||
getProxySlots: () => Promise<{ proxyTarget: Object, proxyHandler: Object }>
|
||||
};
|
||||
|
||||
export type LongStringClient = {
|
||||
|
@ -117,6 +117,12 @@ async function getFullText(
|
||||
});
|
||||
}
|
||||
|
||||
async function getProxySlots(
|
||||
objectClient: ObjectClient
|
||||
): Promise<{ proxyTarget?: Object, proxyHandler?: Object }> {
|
||||
return objectClient.getProxySlots();
|
||||
}
|
||||
|
||||
function iteratorSlice(
|
||||
iterator: PropertiesIterator,
|
||||
start: ?number,
|
||||
@ -137,5 +143,6 @@ module.exports = {
|
||||
enumNonIndexedProperties,
|
||||
enumSymbols,
|
||||
getPrototype,
|
||||
getFullText
|
||||
getFullText,
|
||||
getProxySlots
|
||||
};
|
||||
|
@ -8,7 +8,8 @@ const {
|
||||
enumNonIndexedProperties,
|
||||
getPrototype,
|
||||
enumSymbols,
|
||||
getFullText
|
||||
getFullText,
|
||||
getProxySlots
|
||||
} = require("./client");
|
||||
|
||||
const {
|
||||
@ -77,6 +78,10 @@ function loadItemProperties(
|
||||
promises.push(getFullText(createLongStringClient(value), item));
|
||||
}
|
||||
|
||||
if (shouldLoadItemProxySlots(item, loadedProperties)) {
|
||||
promises.push(getProxySlots(getObjectClient()));
|
||||
}
|
||||
|
||||
return Promise.all(promises).then(mergeResponses);
|
||||
}
|
||||
|
||||
@ -99,6 +104,11 @@ function mergeResponses(responses: Array<Object>): Object {
|
||||
if (response.fullText) {
|
||||
data.fullText = response.fullText;
|
||||
}
|
||||
|
||||
if (response.proxyTarget && response.proxyHandler) {
|
||||
data.proxyTarget = response.proxyTarget;
|
||||
data.proxyHandler = response.proxyHandler;
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
@ -173,7 +183,8 @@ function shouldLoadItemPrototype(
|
||||
!nodeIsDefaultProperties(item) &&
|
||||
!nodeHasAccessors(item) &&
|
||||
!nodeIsPrimitive(item) &&
|
||||
!nodeIsLongString(item)
|
||||
!nodeIsLongString(item) &&
|
||||
!nodeIsProxy(item)
|
||||
);
|
||||
}
|
||||
|
||||
@ -204,6 +215,13 @@ function shouldLoadItemFullText(
|
||||
return !loadedProperties.has(item.path) && nodeIsLongString(item);
|
||||
}
|
||||
|
||||
function shouldLoadItemProxySlots(
|
||||
item: Node,
|
||||
loadedProperties: LoadedProperties = new Map()
|
||||
): boolean {
|
||||
return !loadedProperties.has(item.path) && nodeIsProxy(item);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
loadItemProperties,
|
||||
mergeResponses,
|
||||
@ -212,5 +230,6 @@ module.exports = {
|
||||
shouldLoadItemNonIndexedProperties,
|
||||
shouldLoadItemPrototype,
|
||||
shouldLoadItemSymbols,
|
||||
shouldLoadItemFullText
|
||||
shouldLoadItemFullText,
|
||||
shouldLoadItemProxySlots
|
||||
};
|
||||
|
@ -346,8 +346,11 @@ function makeNodesForPromiseProperties(item: Node): Array<Node> {
|
||||
return properties;
|
||||
}
|
||||
|
||||
function makeNodesForProxyProperties(item: Node): Array<Node> {
|
||||
const { proxyHandler, proxyTarget } = getValue(item);
|
||||
function makeNodesForProxyProperties(
|
||||
loadedProps: GripProperties,
|
||||
item: Node
|
||||
): Array<Node> {
|
||||
const { proxyHandler, proxyTarget } = loadedProps;
|
||||
|
||||
return [
|
||||
createNode({
|
||||
@ -792,8 +795,8 @@ function getChildren(options: {
|
||||
return addToCache(makeNodesForMapEntry(item));
|
||||
}
|
||||
|
||||
if (nodeIsProxy(item)) {
|
||||
return addToCache(makeNodesForProxyProperties(item));
|
||||
if (nodeIsProxy(item) && hasLoadedProps) {
|
||||
return addToCache(makeNodesForProxyProperties(loadedProps, item));
|
||||
}
|
||||
|
||||
if (nodeIsLongString(item) && hasLoadedProps) {
|
||||
|
@ -303,22 +303,6 @@ stubs.set("testProxy", {
|
||||
type: "object",
|
||||
actor: "server1.conn1.child1/obj47",
|
||||
class: "Proxy",
|
||||
proxyTarget: {
|
||||
type: "object",
|
||||
actor: "server1.conn1.child1/obj48",
|
||||
class: "Object",
|
||||
ownPropertyLength: 1
|
||||
},
|
||||
proxyHandler: {
|
||||
type: "object",
|
||||
actor: "server1.conn1.child1/obj49",
|
||||
class: "Array",
|
||||
ownPropertyLength: 4,
|
||||
preview: {
|
||||
kind: "ArrayLike",
|
||||
length: 3
|
||||
}
|
||||
},
|
||||
preview: {
|
||||
kind: "Object",
|
||||
ownProperties: {
|
||||
@ -346,6 +330,24 @@ stubs.set("testProxy", {
|
||||
ownPropertiesLength: 2
|
||||
}
|
||||
});
|
||||
stubs.set("testProxySlots", {
|
||||
proxyTarget: {
|
||||
type: "object",
|
||||
actor: "server1.conn1.child1/obj48",
|
||||
class: "Object",
|
||||
ownPropertyLength: 1
|
||||
},
|
||||
proxyHandler: {
|
||||
type: "object",
|
||||
actor: "server1.conn1.child1/obj49",
|
||||
class: "Array",
|
||||
ownPropertyLength: 4,
|
||||
preview: {
|
||||
kind: "ArrayLike",
|
||||
length: 3
|
||||
}
|
||||
}
|
||||
});
|
||||
stubs.set("testArrayBuffer", {
|
||||
type: "object",
|
||||
actor: "server1.conn1.child1/obj170",
|
||||
|
@ -124,7 +124,8 @@ describe("Grip - Proxy", () => {
|
||||
|
||||
it("renders as expected", () => {
|
||||
const renderRep = props => shallowRenderRep(object, props);
|
||||
const handlerLength = getGripLengthBubbleText(object.proxyHandler, {
|
||||
const handler = object.preview.ownProperties["<handler>"].value;
|
||||
const handlerLength = getGripLengthBubbleText(handler, {
|
||||
mode: MODE.TINY
|
||||
});
|
||||
const out = `Proxy { <target>: {…}, <handler>: ${handlerLength} […] }`;
|
||||
|
@ -1712,8 +1712,8 @@ function makeNodesForPromiseProperties(item) {
|
||||
return properties;
|
||||
}
|
||||
|
||||
function makeNodesForProxyProperties(item) {
|
||||
const { proxyHandler, proxyTarget } = getValue(item);
|
||||
function makeNodesForProxyProperties(loadedProps, item) {
|
||||
const { proxyHandler, proxyTarget } = loadedProps;
|
||||
|
||||
return [createNode({
|
||||
parent: item,
|
||||
@ -2106,8 +2106,8 @@ function getChildren(options) {
|
||||
return addToCache(makeNodesForMapEntry(item));
|
||||
}
|
||||
|
||||
if (nodeIsProxy(item)) {
|
||||
return addToCache(makeNodesForProxyProperties(item));
|
||||
if (nodeIsProxy(item) && hasLoadedProps) {
|
||||
return addToCache(makeNodesForProxyProperties(loadedProps, item));
|
||||
}
|
||||
|
||||
if (nodeIsLongString(item) && hasLoadedProps) {
|
||||
@ -3481,7 +3481,8 @@ const {
|
||||
enumNonIndexedProperties,
|
||||
getPrototype,
|
||||
enumSymbols,
|
||||
getFullText
|
||||
getFullText,
|
||||
getProxySlots
|
||||
} = __webpack_require__(197);
|
||||
|
||||
const {
|
||||
@ -3535,6 +3536,10 @@ function loadItemProperties(item, createObjectClient, createLongStringClient, lo
|
||||
promises.push(getFullText(createLongStringClient(value), item));
|
||||
}
|
||||
|
||||
if (shouldLoadItemProxySlots(item, loadedProperties)) {
|
||||
promises.push(getProxySlots(getObjectClient()));
|
||||
}
|
||||
|
||||
return Promise.all(promises).then(mergeResponses);
|
||||
}
|
||||
|
||||
@ -3557,6 +3562,11 @@ function mergeResponses(responses) {
|
||||
if (response.fullText) {
|
||||
data.fullText = response.fullText;
|
||||
}
|
||||
|
||||
if (response.proxyTarget && response.proxyHandler) {
|
||||
data.proxyTarget = response.proxyTarget;
|
||||
data.proxyHandler = response.proxyHandler;
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
@ -3590,7 +3600,7 @@ function shouldLoadItemEntries(item, loadedProperties = new Map()) {
|
||||
function shouldLoadItemPrototype(item, loadedProperties = new Map()) {
|
||||
const value = getValue(item);
|
||||
|
||||
return value && !loadedProperties.has(item.path) && !nodeIsBucket(item) && !nodeIsMapEntry(item) && !nodeIsEntries(item) && !nodeIsDefaultProperties(item) && !nodeHasAccessors(item) && !nodeIsPrimitive(item) && !nodeIsLongString(item);
|
||||
return value && !loadedProperties.has(item.path) && !nodeIsBucket(item) && !nodeIsMapEntry(item) && !nodeIsEntries(item) && !nodeIsDefaultProperties(item) && !nodeHasAccessors(item) && !nodeIsPrimitive(item) && !nodeIsLongString(item) && !nodeIsProxy(item);
|
||||
}
|
||||
|
||||
function shouldLoadItemSymbols(item, loadedProperties = new Map()) {
|
||||
@ -3603,6 +3613,10 @@ function shouldLoadItemFullText(item, loadedProperties = new Map()) {
|
||||
return !loadedProperties.has(item.path) && nodeIsLongString(item);
|
||||
}
|
||||
|
||||
function shouldLoadItemProxySlots(item, loadedProperties = new Map()) {
|
||||
return !loadedProperties.has(item.path) && nodeIsProxy(item);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
loadItemProperties,
|
||||
mergeResponses,
|
||||
@ -3611,7 +3625,8 @@ module.exports = {
|
||||
shouldLoadItemNonIndexedProperties,
|
||||
shouldLoadItemPrototype,
|
||||
shouldLoadItemSymbols,
|
||||
shouldLoadItemFullText
|
||||
shouldLoadItemFullText,
|
||||
shouldLoadItemProxySlots
|
||||
};
|
||||
|
||||
/***/ }),
|
||||
@ -3706,6 +3721,10 @@ async function getFullText(longStringClient, item) {
|
||||
});
|
||||
}
|
||||
|
||||
async function getProxySlots(objectClient) {
|
||||
return objectClient.getProxySlots();
|
||||
}
|
||||
|
||||
function iteratorSlice(iterator, start, end) {
|
||||
start = start || 0;
|
||||
const count = end ? end - start + 1 : iterator.count;
|
||||
@ -3722,7 +3741,8 @@ module.exports = {
|
||||
enumNonIndexedProperties,
|
||||
enumSymbols,
|
||||
getPrototype,
|
||||
getFullText
|
||||
getFullText,
|
||||
getProxySlots
|
||||
};
|
||||
|
||||
/***/ }),
|
||||
|
@ -318,16 +318,18 @@ VariablesViewController.prototype = {
|
||||
*/
|
||||
_populateFromObject: function(aTarget, aGrip) {
|
||||
if (aGrip.class === "Proxy") {
|
||||
this.addExpander(
|
||||
aTarget.addItem("<target>", { value: aGrip.proxyTarget }, { internalItem: true }),
|
||||
aGrip.proxyTarget);
|
||||
this.addExpander(
|
||||
aTarget.addItem("<handler>", { value: aGrip.proxyHandler }, { internalItem: true }),
|
||||
aGrip.proxyHandler);
|
||||
|
||||
// Refuse to play the proxy's stupid game and return immediately
|
||||
// Refuse to play the proxy's stupid game and just expose the target and handler.
|
||||
const deferred = defer();
|
||||
const objectClient = this._getObjectClient(aGrip);
|
||||
objectClient.getProxySlots(aResponse => {
|
||||
const target = aTarget.addItem("<target>", { value: aResponse.proxyTarget },
|
||||
{ internalItem: true });
|
||||
this.addExpander(target, aResponse.proxyTarget);
|
||||
const handler = aTarget.addItem("<handler>", { value: aResponse.proxyHandler },
|
||||
{ internalItem: true });
|
||||
this.addExpander(handler, aResponse.proxyHandler);
|
||||
deferred.resolve();
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
|
@ -358,6 +358,7 @@ skip-if = true # Bug 1438979
|
||||
[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_nested_proxy.js]
|
||||
[browser_webconsole_object_inspector_selected_text.js]
|
||||
[browser_webconsole_object_inspector_scroll.js]
|
||||
[browser_webconsole_object_inspector_while_debugging_and_inspecting.js]
|
||||
|
@ -0,0 +1,50 @@
|
||||
/* -*- 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 and expanding getters in the console.
|
||||
const TEST_URI = "data:text/html;charset=utf8,"
|
||||
+ "<h1>Object Inspector on deeply nested proxies</h1>";
|
||||
|
||||
add_task(async function() {
|
||||
const hud = await openNewTabAndConsole(TEST_URI);
|
||||
|
||||
await ContentTask.spawn(gBrowser.selectedBrowser, null, function() {
|
||||
let proxy = new Proxy({}, {});
|
||||
for (let i = 0; i < 1e5; ++i) {
|
||||
proxy = new Proxy(proxy, proxy);
|
||||
}
|
||||
content.wrappedJSObject.console.log("oi-test", proxy);
|
||||
});
|
||||
|
||||
const node = await waitFor(() => findMessage(hud, "oi-test"));
|
||||
const oi = node.querySelector(".tree");
|
||||
const [proxyNode] = getObjectInspectorNodes(oi);
|
||||
|
||||
expandObjectInspectorNode(proxyNode);
|
||||
await waitFor(() => getObjectInspectorNodes(oi).length > 1);
|
||||
checkChildren(proxyNode, [`<target>`, `<handler>`]);
|
||||
|
||||
const targetNode = findObjectInspectorNode(oi, "<target>");
|
||||
expandObjectInspectorNode(targetNode);
|
||||
await waitFor(() => getObjectInspectorChildrenNodes(targetNode).length > 0);
|
||||
checkChildren(targetNode, [`<target>`, `<handler>`]);
|
||||
|
||||
const handlerNode = findObjectInspectorNode(oi, "<handler>");
|
||||
expandObjectInspectorNode(handlerNode);
|
||||
await waitFor(() => getObjectInspectorChildrenNodes(handlerNode).length > 0);
|
||||
checkChildren(handlerNode, [`<target>`, `<handler>`]);
|
||||
});
|
||||
|
||||
function checkChildren(node, expectedChildren) {
|
||||
const children = getObjectInspectorChildrenNodes(node);
|
||||
is(children.length, expectedChildren.length,
|
||||
"There is the expected number of children");
|
||||
children.forEach((child, index) => {
|
||||
ok(child.textContent.includes(expectedChildren[index]),
|
||||
`Expected "${expectedChildren[index]}" child`);
|
||||
});
|
||||
}
|
@ -865,6 +865,24 @@ const proto = {
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle a protocol request to get the target and handler internal slots of a proxy.
|
||||
*/
|
||||
proxySlots: function() {
|
||||
// There could be transparent security wrappers, unwrap to check if it's a proxy.
|
||||
// However, retrieve proxyTarget and proxyHandler from `this.obj` to avoid exposing
|
||||
// the unwrapped target and handler.
|
||||
const unwrapped = DevToolsUtils.unwrap(this.obj);
|
||||
if (!unwrapped || !unwrapped.isProxy) {
|
||||
return this.throwError("objectNotProxy",
|
||||
"'proxySlots' request is only valid for grips with a 'Proxy' class.");
|
||||
}
|
||||
return {
|
||||
proxyTarget: this.hooks.createValueGrip(this.obj.proxyTarget),
|
||||
proxyHandler: this.hooks.createValueGrip(this.obj.proxyHandler),
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Release the actor, when it isn't needed anymore.
|
||||
* Protocol.js uses this release method to call the destroy method.
|
||||
|
@ -288,13 +288,15 @@ const previewers = {
|
||||
}],
|
||||
|
||||
Proxy: [function({obj, hooks}, grip, rawObj) {
|
||||
// Only preview top-level proxies, avoiding recursion. Otherwise, since both the
|
||||
// target and handler can also be proxies, we could get an exponential behavior.
|
||||
if (hooks.getGripDepth() > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// The `isProxy` getter of the debuggee object only detects proxies without
|
||||
// security wrappers. If false, the target and handler are not available.
|
||||
const hasTargetAndHandler = obj.isProxy;
|
||||
if (hasTargetAndHandler) {
|
||||
grip.proxyTarget = hooks.createValueGrip(obj.proxyTarget);
|
||||
grip.proxyHandler = hooks.createValueGrip(obj.proxyHandler);
|
||||
}
|
||||
|
||||
grip.preview = {
|
||||
kind: "Object",
|
||||
@ -302,13 +304,11 @@ const previewers = {
|
||||
ownPropertiesLength: 2 * hasTargetAndHandler,
|
||||
};
|
||||
|
||||
if (hooks.getGripDepth() > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hasTargetAndHandler) {
|
||||
grip.preview.ownProperties["<target>"] = {value: grip.proxyTarget};
|
||||
grip.preview.ownProperties["<handler>"] = {value: grip.proxyHandler};
|
||||
Object.assign(grip.preview.ownProperties, {
|
||||
"<target>": {value: hooks.createValueGrip(obj.proxyTarget)},
|
||||
"<handler>": {value: hooks.createValueGrip(obj.proxyHandler)},
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -57,8 +57,12 @@ function test({ threadClient, debuggee }, testOptions) {
|
||||
// Check the grip of the proxy object.
|
||||
check_proxy_grip(debuggee, testOptions, proxyGrip);
|
||||
|
||||
// Check the prototype and properties of the proxy object.
|
||||
// Check the target and handler slots of the proxy object.
|
||||
const proxyClient = threadClient.pauseGrip(proxyGrip);
|
||||
const proxySlots = await proxyClient.getProxySlots();
|
||||
check_proxy_slots(debuggee, testOptions, proxyGrip, proxySlots);
|
||||
|
||||
// Check the prototype and properties of the proxy object.
|
||||
const proxyResponse = await proxyClient.getPrototypeAndProperties();
|
||||
check_properties(testOptions, proxyResponse.ownProperties, true, false);
|
||||
check_prototype(debuggee, testOptions, proxyResponse.prototype, true, false);
|
||||
@ -117,20 +121,17 @@ function check_proxy_grip(debuggee, testOptions, grip) {
|
||||
if (global === debuggee) {
|
||||
// The proxy has no security wrappers.
|
||||
strictEqual(grip.class, "Proxy", "The grip has a Proxy class.");
|
||||
ok(grip.proxyTarget, "There is a [[ProxyTarget]] grip.");
|
||||
ok(grip.proxyHandler, "There is a [[ProxyHandler]] grip.");
|
||||
strictEqual(preview.ownPropertiesLength, 2, "The preview has 2 properties.");
|
||||
const target = preview.ownProperties["<target>"].value;
|
||||
strictEqual(target, grip.proxyTarget, "<target> contains the [[ProxyTarget]].");
|
||||
const handler = preview.ownProperties["<handler>"].value;
|
||||
strictEqual(handler, grip.proxyHandler, "<handler> contains the [[ProxyHandler]].");
|
||||
const props = preview.ownProperties;
|
||||
ok(props["<target>"].value, "<target> contains the [[ProxyTarget]].");
|
||||
ok(props["<handler>"].value, "<handler> contains the [[ProxyHandler]].");
|
||||
} else if (isOpaque) {
|
||||
// The proxy has opaque security wrappers.
|
||||
strictEqual(grip.class, "Opaque", "The grip has an Opaque class.");
|
||||
strictEqual(grip.ownPropertyLength, 0, "The grip has no properties.");
|
||||
} else if (!subsumes) {
|
||||
// The proxy belongs to compartment not subsumed by the debuggee.
|
||||
strictEqual(grip.class, "Restricted", "The grip has an Restricted class.");
|
||||
strictEqual(grip.class, "Restricted", "The grip has a Restricted class.");
|
||||
ok(!("ownPropertyLength" in grip), "The grip doesn't know the number of properties.");
|
||||
} else if (globalIsInvisible) {
|
||||
// The proxy belongs to an invisible-to-debugger compartment.
|
||||
@ -140,14 +141,28 @@ function check_proxy_grip(debuggee, testOptions, grip) {
|
||||
} else {
|
||||
// The proxy has non-opaque security wrappers.
|
||||
strictEqual(grip.class, "Proxy", "The grip has a Proxy class.");
|
||||
ok(!("proxyTarget" in grip), "There is no [[ProxyTarget]] grip.");
|
||||
ok(!("proxyHandler" in grip), "There is no [[ProxyHandler]] grip.");
|
||||
strictEqual(preview.ownPropertiesLength, 0, "The preview has no properties.");
|
||||
ok(!("<target>" in preview), "The preview has no <target> property.");
|
||||
ok(!("<handler>" in preview), "The preview has no <handler> property.");
|
||||
}
|
||||
}
|
||||
|
||||
function check_proxy_slots(debuggee, testOptions, grip, proxySlots) {
|
||||
const { global } = testOptions;
|
||||
|
||||
if (grip.class !== "Proxy") {
|
||||
strictEqual(proxySlots, undefined, "Slots can only be retrived for Proxy grips.");
|
||||
} else if (global === debuggee) {
|
||||
const { proxyTarget, proxyHandler } = proxySlots;
|
||||
strictEqual(proxyTarget.type, "object", "There is a [[ProxyTarget]] grip.");
|
||||
strictEqual(proxyHandler.type, "object", "There is a [[ProxyHandler]] grip.");
|
||||
} else {
|
||||
const { proxyTarget, proxyHandler } = proxySlots;
|
||||
strictEqual(proxyTarget.type, "undefined", "There is no [[ProxyTarget]] grip.");
|
||||
strictEqual(proxyHandler.type, "undefined", "There is no [[ProxyHandler]] grip.");
|
||||
}
|
||||
}
|
||||
|
||||
function check_properties(testOptions, props, isProxy, createdInDebuggee) {
|
||||
const { subsumes, globalIsInvisible } = testOptions;
|
||||
const ownPropertiesLength = Reflect.ownKeys(props).length;
|
||||
|
36
devtools/server/tests/unit/test_objectgrips-nested-proxy.js
Normal file
36
devtools/server/tests/unit/test_objectgrips-nested-proxy.js
Normal file
@ -0,0 +1,36 @@
|
||||
/* 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 }) => {
|
||||
await new Promise(function(resolve) {
|
||||
threadClient.addOneTimeListener("paused", async function(event, packet) {
|
||||
const [grip] = packet.frame.arguments;
|
||||
const objClient = threadClient.pauseGrip(grip);
|
||||
const {proxyTarget, proxyHandler} = await objClient.getProxySlots();
|
||||
|
||||
strictEqual(grip.class, "Proxy", "Its a proxy grip.");
|
||||
strictEqual(proxyTarget.class, "Proxy", "The target is also a proxy.");
|
||||
strictEqual(proxyHandler.class, "Proxy", "The handler is also a proxy.");
|
||||
|
||||
await threadClient.resume();
|
||||
resolve();
|
||||
});
|
||||
debuggee.eval(function stopMe(arg) {
|
||||
debugger;
|
||||
}.toString());
|
||||
debuggee.eval(`
|
||||
var proxy = new Proxy({}, {});
|
||||
for (let i = 0; i < 1e5; ++i)
|
||||
proxy = new Proxy(proxy, proxy);
|
||||
stopMe(proxy);
|
||||
`);
|
||||
});
|
||||
}));
|
@ -170,6 +170,7 @@ skip-if = true # breakpoint sliding is not supported bug 1525685
|
||||
[test_objectgrips-fn-apply-01.js]
|
||||
[test_objectgrips-fn-apply-02.js]
|
||||
[test_objectgrips-fn-apply-03.js]
|
||||
[test_objectgrips-nested-proxy.js]
|
||||
[test_promise_state-01.js]
|
||||
[test_promise_state-02.js]
|
||||
[test_promise_state-03.js]
|
||||
|
@ -359,10 +359,11 @@ DebuggerClient.prototype = {
|
||||
const deferred = promise.defer();
|
||||
function listenerJson(resp) {
|
||||
removeRequestListeners();
|
||||
resp = safeOnResponse(resp);
|
||||
if (resp.error) {
|
||||
deferred.reject(safeOnResponse(resp));
|
||||
deferred.reject(resp);
|
||||
} else {
|
||||
deferred.resolve(safeOnResponse(resp));
|
||||
deferred.resolve(resp);
|
||||
}
|
||||
}
|
||||
function listenerBulk(resp) {
|
||||
|
@ -303,6 +303,29 @@ ObjectClient.prototype = {
|
||||
return packet;
|
||||
},
|
||||
}),
|
||||
|
||||
/**
|
||||
* Request the target and handler internal slots of a proxy.
|
||||
*/
|
||||
getProxySlots: DebuggerClient.requester({
|
||||
type: "proxySlots",
|
||||
}, {
|
||||
before: function(packet) {
|
||||
if (this._grip.class !== "Proxy") {
|
||||
throw new Error("getProxySlots is only valid for proxy grips.");
|
||||
}
|
||||
return packet;
|
||||
},
|
||||
after: function(response) {
|
||||
// Before Firefox 68 (bug 1392760), the proxySlots request didn't exist.
|
||||
// The proxy target and handler were directly included in the grip.
|
||||
if (response.error === "unrecognizedPacketType") {
|
||||
const {proxyTarget, proxyHandler} = this._grip;
|
||||
return {proxyTarget, proxyHandler};
|
||||
}
|
||||
return response;
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
module.exports = ObjectClient;
|
||||
|
@ -102,6 +102,11 @@ types.addDictType("object.originalSourceLocation", {
|
||||
functionDisplayName: "string",
|
||||
});
|
||||
|
||||
types.addDictType("object.proxySlots", {
|
||||
proxyTarget: "object.descriptor",
|
||||
proxyHandler: "object.descriptor",
|
||||
});
|
||||
|
||||
const objectSpec = generateActorSpec({
|
||||
typeName: "obj",
|
||||
|
||||
@ -198,6 +203,10 @@ const objectSpec = generateActorSpec({
|
||||
rejectionStack: RetVal("array:object.originalSourceLocation"),
|
||||
},
|
||||
},
|
||||
proxySlots: {
|
||||
request: {},
|
||||
response: RetVal("object.proxySlots"),
|
||||
},
|
||||
release: { release: true },
|
||||
scope: {
|
||||
request: {},
|
||||
|
Loading…
Reference in New Issue
Block a user