Bug 1617210 - Fix issue with duplicated ObjectFronts in the scopes panel. r=jlast.

Since the thread actor caches the object actors
it creates, if an object had multiple properties
referencing the same object, we would create
multiple fronts for the same object actor, which
would confuse protocol.js.
The fix consist in checking if a front already exists
before creating a new one.
A test is added for the debugger to ensure this
works as expected and we don't regress.

Differential Revision: https://phabricator.services.mozilla.com/D63907

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Nicolas Chevobbe 2020-02-25 10:22:31 +00:00
parent 14600c1dc3
commit ab439621fd
6 changed files with 103 additions and 0 deletions

View File

@ -180,6 +180,7 @@ skip-if = (os == 'linux' && debug) || (os == 'linux' && asan) || ccov #Bug 1456
[browser_dbg-old-breakpoint.js]
[browser_dbg-idb-run-to-completion.js]
[browser_dbg-inline-script-offset.js]
[browser_dbg-scopes-duplicated.js]
[browser_dbg-scopes-xrays.js]
[browser_dbg-merge-scopes.js]
[browser_dbg-message-run-to-completion.js]

View File

@ -0,0 +1,91 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that properties with the same value objec be expanded. See Bug 1617210.
const httpServer = createTestHTTPServer();
httpServer.registerContentType("html", "text/html");
httpServer.registerContentType("js", "application/javascript");
httpServer.registerPathHandler(`/`, function(request, response) {
response.setStatusLine(request.httpVersion, 200, "OK");
response.write(`
<html>
<button class="pause">Click me</button>
<script type="text/javascript" src="test.js"></script>
</html>`);
});
httpServer.registerPathHandler("/test.js", function(request, response) {
response.setHeader("Content-Type", "application/javascript");
response.write(`
document.addEventListener("click", function onClick(e) {
var hello = {hello: "world"};
var x = {a: hello, b: hello, c: hello};
debugger;
});
`);
});
const port = httpServer.identity.primaryPort;
const TEST_URL = `http://localhost:${port}/`;
add_task(async function() {
const dbg = await initDebuggerWithAbsoluteURL(TEST_URL);
const ready = Promise.all([
waitForPaused(dbg),
waitForLoadedSource(dbg, "test"),
]);
SpecialPowers.spawn(gBrowser.selectedBrowser, [], function() {
content.document.querySelector("button.pause").click();
});
await ready;
is(getLabel(dbg, 4), "e");
is(getLabel(dbg, 5), "hello");
is(getLabel(dbg, 6), "x");
info("Expand `x` node");
await toggleScopeNode(dbg, 6);
is(getLabel(dbg, 7), "a");
is(getLabel(dbg, 8), "b");
is(getLabel(dbg, 9), "c");
info("Expand `c`, `b` and `a` nodes");
await toggleScopeNode(dbg, 9);
await toggleScopeNode(dbg, 8);
await toggleScopeNode(dbg, 7);
is(getLabel(dbg, 7), "a");
is(getLabel(dbg, 8), "hello");
is(getLabel(dbg, 9), "<prototype>");
is(getLabel(dbg, 10), "b");
is(getLabel(dbg, 11), "hello");
is(getLabel(dbg, 12), "<prototype>");
is(getLabel(dbg, 13), "c");
is(getLabel(dbg, 14), "hello");
is(getLabel(dbg, 15), "<prototype>");
info("Expand `e`");
await toggleScopeNode(dbg, 4);
info("Expand the `target` node");
let nodes = getAllLabels(dbg);
const originalNodesCount = nodes.length;
const targetNodeIndex = nodes.indexOf("target");
ok(targetNodeIndex > -1, "Found the target node");
await toggleScopeNode(dbg, targetNodeIndex);
nodes = getAllLabels(dbg);
ok(nodes.length > originalNodesCount, "the target node was expanded");
ok(nodes.includes("classList"), "classList is displayed");
});
function getAllLabels(dbg) {
return Array.from(findAllElements(dbg, "scopeNodes")).map(el => el.innerText);
}
function getLabel(dbg, index) {
return findElement(dbg, "scopeNode", index).innerText;
}

View File

@ -1299,6 +1299,7 @@ const selectors = {
},
columnBreakpoints: ".column-breakpoint",
scopes: ".scopes-list",
scopeNodes: ".scopes-list .object-label",
scopeNode: i => `.scopes-list .tree-node:nth-child(${i}) .object-label`,
scopeValue: i =>
`.scopes-list .tree-node:nth-child(${i}) .object-delimiter + *`,

View File

@ -433,6 +433,7 @@ function parsePacketAndCreateFronts(packet) {
conn: {
poolFor: () => {},
addActorPool: () => {},
getFrontByID: () => {},
},
manage: () => {},
});

View File

@ -310,6 +310,7 @@ async function test_unsafe_grips(
);
}
}
await objClient.release();
}
await threadFront.resume();

View File

@ -315,6 +315,14 @@ function getAdHocFrontOrPrimitiveGrip(packet, parentFront) {
}
const { conn, targetFront } = parentFront;
// We may have already created a front for this object actor since some actor (e.g. the
// thread actor) cache the object actors they create.
const existingFront = conn.getFrontByID(packet.actor);
if (existingFront) {
return existingFront;
}
const { type } = packet;
if (type === "longString") {