Bug 1424721 - Allow long strings and invisible-to-debugger objects to be stored as global variables. r=nchevobbe

MozReview-Commit-ID: IZFKgror7F6

--HG--
extra : rebase_source : ef2073c1338701a3b1d654975e29d21224481c47
This commit is contained in:
Oriol Brufau 2017-12-18 12:52:31 +01:00
parent 0e5ba4ebd8
commit c9ff031c33
3 changed files with 74 additions and 57 deletions

View File

@ -12,72 +12,61 @@ const TEST_URI = `data:text/html;charset=utf-8,<script>
window.bar = { baz: 1 };
console.log("foo");
console.log("foo", window.bar);
console.log(["foo", window.bar, 2]);
window.array = ["foo", window.bar, 2];
console.log(window.array);
window.longString = "foo" + "a".repeat(1e4);
console.log(window.longString);
</script>`;
add_task(async function() {
let hud = await openNewTabAndConsole(TEST_URI);
let [msgWithText, msgWithObj, msgNested] =
await waitFor(() => findMessages(hud, "foo"));
ok(msgWithText && msgWithObj && msgNested, "Three messages should have appeared");
let text = msgWithText.querySelector(".objectBox-string");
let objInMsgWithObj = msgWithObj.querySelector(".objectBox-object");
let textInMsgWithObj = msgWithObj.querySelector(".objectBox-string");
// The third message has an object nested in an array, the array is therefore the top
// object, the object is the nested object.
let topObjInMsg = msgNested.querySelector(".objectBox-array");
let nestedObjInMsg = msgNested.querySelector(".objectBox-object");
let messages = await waitFor(() => findMessages(hud, "foo"));
is(messages.length, 4, "Four messages should have appeared");
let [msgWithText, msgWithObj, msgNested, msgLongStr] = messages;
let varIdx = 0;
info("Check store as global variable is disabled for text only messages");
let menuPopup = await openContextMenu(hud, text);
let storeMenuItem = menuPopup.querySelector("#console-menu-store");
ok(storeMenuItem.disabled, "store as global variable is disabled for text message");
await hideContextMenu(hud);
await storeAsVariable(hud, msgWithText, "string");
info("Check store as global variable is disabled for text in complex messages");
menuPopup = await openContextMenu(hud, textInMsgWithObj);
storeMenuItem = menuPopup.querySelector("#console-menu-store");
ok(storeMenuItem.disabled,
"store as global variable is disabled for text in complex message");
await hideContextMenu(hud);
await storeAsVariable(hud, msgWithObj, "string");
info("Check store as global variable is enabled for objects in complex messages");
await storeAsVariable(hud, objInMsgWithObj);
is(hud.jsterm.getInputValue(), "temp0", "Input was set");
let executedResult = await hud.jsterm.execute();
ok(executedResult.textContent.includes("{ baz: 1 }"),
"Correct variable assigned into console");
await storeAsVariable(hud, msgWithObj, "object", varIdx++, "window.bar");
info("Check store as global variable is enabled for top object in nested messages");
await storeAsVariable(hud, topObjInMsg);
is(hud.jsterm.getInputValue(), "temp1", "Input was set");
executedResult = await hud.jsterm.execute();
ok(executedResult.textContent.includes(`[ "foo", {\u2026}, 2 ]`),
"Correct variable assigned into console " + executedResult.textContent);
await storeAsVariable(hud, msgNested, "array", varIdx++, "window.array");
info("Check store as global variable is enabled for nested object in nested messages");
await storeAsVariable(hud, nestedObjInMsg);
await storeAsVariable(hud, msgNested, "object", varIdx++, "window.bar");
is(hud.jsterm.getInputValue(), "temp2", "Input was set");
info("Check store as global variable is enabled for long strings");
await storeAsVariable(hud, msgLongStr, "string", varIdx++, "window.longString");
executedResult = await hud.jsterm.execute();
ok(executedResult.textContent.includes("{ baz: 1 }"),
"Correct variable assigned into console " + executedResult.textContent);
info("Check store as global variable is enabled for invisible-to-debugger objects");
let onMessageInvisible = waitForMessage(hud, "foo");
ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
let obj = Cu.Sandbox(Cu.getObjectPrincipal(content), {invisibleToDebugger: true});
content.wrappedJSObject.invisibleToDebugger = obj;
content.console.log("foo", obj);
});
let msgInvisible = (await onMessageInvisible).node;
await storeAsVariable(hud, msgInvisible, "object", varIdx++, "window.invisibleToDebugger");
});
async function storeAsVariable(hud, element) {
info("Check store as global variable is enabled");
async function storeAsVariable(hud, msg, type, varIdx, equalTo) {
let element = msg.querySelector(".objectBox-" + type);
let menuPopup = await openContextMenu(hud, element);
let storeMenuItem = menuPopup.querySelector("#console-menu-store");
ok(!storeMenuItem.disabled,
"store as global variable is enabled for object in complex message");
if (varIdx == null) {
ok(storeMenuItem.disabled, "store as global variable is disabled");
await hideContextMenu(hud);
return;
}
ok(!storeMenuItem.disabled, "store as global variable is enabled");
info("Click on store as global variable");
let onceInputSet = hud.jsterm.once("set-input-value");
@ -88,4 +77,9 @@ async function storeAsVariable(hud, element) {
info("Wait for context menu to be hidden");
await hideContextMenu(hud);
is(hud.jsterm.getInputValue(), "temp" + varIdx, "Input was set");
let equal = await hud.jsterm.requestEvaluation("temp" + varIdx + " === " + equalTo);
is(equal.result, true, "Correct variable assigned into console.");
}

View File

@ -69,6 +69,10 @@ function ObjectActor(obj, {
ObjectActor.prototype = {
actorPrefix: "obj",
rawValue: function () {
return this.obj.unsafeDereference();
},
/**
* Returns a grip for this actor for returning in a protocol message.
*/
@ -2270,6 +2274,10 @@ function LongStringActor(string) {
LongStringActor.prototype = {
actorPrefix: "longString",
rawValue: function () {
return this.string;
},
destroy: function () {
// Because longStringActors is not a weak map, we won't automatically leave
// it so we need to manually leave on destroy so that we don't leak
@ -2341,6 +2349,10 @@ function ArrayBufferActor(buffer) {
ArrayBufferActor.prototype = {
actorPrefix: "arrayBuffer",
rawValue: function () {
return this.buffer;
},
destroy: function () {
},

View File

@ -42,6 +42,10 @@ if (isWorker) {
loader.lazyRequireGetter(this, "ContentProcessListener", "devtools/server/actors/webconsole/listeners", true);
}
function isObject(value) {
return Object(value) === value;
}
/**
* The WebConsoleActor implements capabilities needed for the Web Console
* feature.
@ -443,8 +447,7 @@ WebConsoleActor.prototype =
* Debuggee value for |value|.
*/
makeDebuggeeValue: function (value, useObjectGlobal) {
let isObject = Object(value) === value;
if (useObjectGlobal && isObject) {
if (useObjectGlobal && isObject(value)) {
try {
let global = Cu.getGlobalForObject(value);
let dbgGlobal = this.dbg.makeGlobalObjectReference(global);
@ -1320,17 +1323,25 @@ WebConsoleActor.prototype =
let objActor = this.getActorByID(options.bindObjectActor ||
options.selectedObjectActor);
if (objActor) {
let jsObj = objActor.obj.unsafeDereference();
// If we use the makeDebuggeeValue method of jsObj's own global, then
// we'll get a D.O that sees jsObj as viewed from its own compartment -
// that is, without wrappers. The evalWithBindings call will then wrap
// jsObj appropriately for the evaluation compartment.
let global = Cu.getGlobalForObject(jsObj);
let _dbgWindow = dbg.makeGlobalObjectReference(global);
bindSelf = dbgWindow.makeDebuggeeValue(jsObj);
let jsVal = objActor.rawValue();
if (options.bindObjectActor) {
dbgWindow = _dbgWindow;
if (isObject(jsVal)) {
// If we use the makeDebuggeeValue method of jsVal's own global, then
// we'll get a D.O that sees jsVal as viewed from its own compartment -
// that is, without wrappers. The evalWithBindings call will then wrap
// jsVal appropriately for the evaluation compartment.
bindSelf = dbgWindow.makeDebuggeeValue(jsVal);
if (options.bindObjectActor) {
let global = Cu.getGlobalForObject(jsVal);
try {
let _dbgWindow = dbg.makeGlobalObjectReference(global);
dbgWindow = _dbgWindow;
} catch (err) {
// The above will throw if `global` is invisible to debugger.
}
}
} else {
bindSelf = jsVal;
}
}
}