diff --git a/browser/devtools/debugger/test/Makefile.in b/browser/devtools/debugger/test/Makefile.in index 88d006acc21e..0e8cc91068d9 100644 --- a/browser/devtools/debugger/test/Makefile.in +++ b/browser/devtools/debugger/test/Makefile.in @@ -33,6 +33,7 @@ MOCHITEST_BROWSER_TESTS = \ browser_dbg_propertyview-09.js \ browser_dbg_propertyview-10.js \ browser_dbg_propertyview-11.js \ + browser_dbg_propertyview-12.js \ browser_dbg_propertyview-edit-value.js \ browser_dbg_propertyview-edit-watch.js \ browser_dbg_propertyview-data-big.js \ diff --git a/browser/devtools/debugger/test/browser_dbg_propertyview-12.js b/browser/devtools/debugger/test/browser_dbg_propertyview-12.js new file mode 100644 index 000000000000..192bcfcc9450 --- /dev/null +++ b/browser/devtools/debugger/test/browser_dbg_propertyview-12.js @@ -0,0 +1,95 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This test checks that we properly set the frozen, sealed, and non-extensbile +// attributes on variables so that the F/S/N is shown in the variables view. + +var gPane = null; +var gTab = null; +var gDebuggee = null; +var gDebugger = null; + +function test() { + debug_tab_pane(STACK_URL, function(aTab, aDebuggee, aPane) { + gTab = aTab; + gDebuggee = aDebuggee; + gPane = aPane; + gDebugger = gPane.panelWin; + + testFSN(); + }); +} + +function testFSN() { + gDebugger.addEventListener("Debugger:FetchedVariables", function _onFetchedVariables() { + gDebugger.removeEventListener("Debugger:FetchedVariables", _onFetchedVariables, false); + runTest(); + }, false); + + gDebuggee.eval("(" + function () { + var frozen = Object.freeze({}); + var sealed = Object.seal({}); + var nonExtensible = Object.preventExtensions({}); + var extensible = {}; + var string = "foo bar baz"; + + debugger; + } + "())"); +} + +function runTest() { + let hasNoneTester = function (aVariable) { + ok(!aVariable.hasAttribute("frozen"), + "The variable should not be frozen"); + ok(!aVariable.hasAttribute("sealed"), + "The variable should not be sealed"); + ok(!aVariable.hasAttribute("non-extensible"), + "The variable should be extensible"); + }; + + let testers = { + frozen: function (aVariable) { + ok(aVariable.hasAttribute("frozen"), + "The variable should be frozen") + }, + sealed: function (aVariable) { + ok(aVariable.hasAttribute("sealed"), + "The variable should be sealed") + }, + nonExtensible: function (aVariable) { + ok(aVariable.hasAttribute("non-extensible"), + "The variable should be non-extensible") + }, + extensible: hasNoneTester, + string: hasNoneTester, + arguments: hasNoneTester, + this: hasNoneTester + }; + + let variables = gDebugger.DebuggerView.Variables._parent + .querySelectorAll(".variable-or-property"); + + for (let v of variables) { + let name = v.querySelector(".name").getAttribute("value"); + let tester = testers[name]; + delete testers[name]; + ok(tester, "We should have a tester for the '" + name + "' variable."); + tester(v); + } + + is(Object.keys(testers).length, 0, + "We should have run and removed all the testers."); + + closeDebuggerAndFinish(); +} + +registerCleanupFunction(function() { + removeTab(gTab); + gPane = null; + gTab = null; + gDebuggee = null; + gDebugger = null; +}); diff --git a/browser/devtools/shared/widgets/VariablesView.jsm b/browser/devtools/shared/widgets/VariablesView.jsm index 83f8b5952b8e..4612ac600c3e 100644 --- a/browser/devtools/shared/widgets/VariablesView.jsm +++ b/browser/devtools/shared/widgets/VariablesView.jsm @@ -2364,21 +2364,15 @@ ViewHelpers.create({ constructor: Variable, proto: Scope.prototype }, { let tooltip = document.createElement("tooltip"); tooltip.id = "tooltip-" + this._idString; - - let configurableLabel = document.createElement("label"); - let enumerableLabel = document.createElement("label"); - let writableLabel = document.createElement("label"); - let safeGetterLabel = document.createElement("label"); - configurableLabel.setAttribute("value", "configurable"); - enumerableLabel.setAttribute("value", "enumerable"); - writableLabel.setAttribute("value", "writable"); - safeGetterLabel.setAttribute("value", "native-getter"); - tooltip.setAttribute("orient", "horizontal"); - tooltip.appendChild(configurableLabel); - tooltip.appendChild(enumerableLabel); - tooltip.appendChild(writableLabel); - tooltip.appendChild(safeGetterLabel); + + let labels = ["configurable", "enumerable", "writable", "native-getter", + "frozen", "sealed", "non-extensible"]; + for (let label of labels) { + let labelElement = document.createElement("label"); + labelElement.setAttribute("value", label); + tooltip.appendChild(labelElement); + } this._target.appendChild(tooltip); this._target.setAttribute("tooltip", tooltip.id); @@ -2408,14 +2402,27 @@ ViewHelpers.create({ constructor: Variable, proto: Scope.prototype }, { if (this.ownerView.eval) { this._target.setAttribute("editable", ""); } - if (!descriptor.null && !descriptor.configurable) { - this._target.setAttribute("non-configurable", ""); - } - if (!descriptor.null && !descriptor.enumerable) { - this._target.setAttribute("non-enumerable", ""); - } - if (!descriptor.null && !descriptor.writable && !this.ownerView.getter && !this.ownerView.setter) { - this._target.setAttribute("non-writable", ""); + if (!descriptor.null) { + if (!descriptor.configurable) { + this._target.setAttribute("non-configurable", ""); + } + if (!descriptor.enumerable) { + this._target.setAttribute("non-enumerable", ""); + } + if (!descriptor.writable && !this.ownerView.getter && !this.ownerView.setter) { + this._target.setAttribute("non-writable", ""); + } + if (descriptor.value && typeof descriptor.value == "object") { + if (descriptor.value.frozen) { + this._target.setAttribute("frozen", ""); + } + if (descriptor.value.sealed) { + this._target.setAttribute("sealed", ""); + } + if (!descriptor.value.extensible) { + this._target.setAttribute("non-extensible", ""); + } + } } if (descriptor && "getterValue" in descriptor) { this._target.setAttribute("safe-getter", ""); diff --git a/browser/themes/linux/devtools/widgets.css b/browser/themes/linux/devtools/widgets.css index 5c065489826d..8bcdd5388bd6 100644 --- a/browser/themes/linux/devtools/widgets.css +++ b/browser/themes/linux/devtools/widgets.css @@ -531,6 +531,21 @@ text-shadow: 0 0 8px #fcc; } +.variable-or-property[non-extensible]:not([non-writable]) > .title:after { + content: "N"; + display: inline-block; +} + +.variable-or-property[sealed]:not([non-writable]) > .title:after { + content: "S"; + display: inline-block; +} + +.variable-or-property[frozen]:not([non-writable]) > .title:after { + content: "F"; + display: inline-block; +} + /* Variables and properties tooltips */ .variable-or-property > tooltip > label { @@ -543,7 +558,10 @@ text-decoration: line-through; } -.variable-or-property:not([safe-getter]) > tooltip > label[value=native-getter] { +.variable-or-property:not([safe-getter]) > tooltip > label[value=native-getter], +.variable-or-property:not([non-extensible]) > tooltip > label[value=non-extensible], +.variable-or-property:not([frozen]) > tooltip > label[value=frozen], +.variable-or-property:not([sealed]) > tooltip > label[value=sealed] { display: none; } diff --git a/browser/themes/osx/devtools/widgets.css b/browser/themes/osx/devtools/widgets.css index 8bccab4d5a76..70ea82b1cb88 100644 --- a/browser/themes/osx/devtools/widgets.css +++ b/browser/themes/osx/devtools/widgets.css @@ -531,6 +531,21 @@ text-shadow: 0 0 8px #fcc; } +.variable-or-property[non-extensible]:not([non-writable]) > .title:after { + content: "N"; + display: inline-block; +} + +.variable-or-property[sealed]:not([non-writable]) > .title:after { + content: "S"; + display: inline-block; +} + +.variable-or-property[frozen]:not([non-writable]) > .title:after { + content: "F"; + display: inline-block; +} + /* Variables and properties tooltips */ .variable-or-property > tooltip > label { @@ -543,7 +558,10 @@ text-decoration: line-through; } -.variable-or-property:not([safe-getter]) > tooltip > label[value=native-getter] { +.variable-or-property:not([safe-getter]) > tooltip > label[value=native-getter], +.variable-or-property:not([non-extensible]) > tooltip > label[value=non-extensible], +.variable-or-property:not([frozen]) > tooltip > label[value=frozen], +.variable-or-property:not([sealed]) > tooltip > label[value=sealed] { display: none; } diff --git a/browser/themes/windows/devtools/widgets.css b/browser/themes/windows/devtools/widgets.css index 9e724f3743fc..8eb19c0e008f 100644 --- a/browser/themes/windows/devtools/widgets.css +++ b/browser/themes/windows/devtools/widgets.css @@ -534,6 +534,21 @@ text-shadow: 0 0 8px #fcc; } +.variable-or-property[non-extensible]:not([non-writable]) > .title:after { + content: "N"; + display: inline-block; +} + +.variable-or-property[sealed]:not([non-writable]) > .title:after { + content: "S"; + display: inline-block; +} + +.variable-or-property[frozen]:not([non-writable]) > .title:after { + content: "F"; + display: inline-block; +} + /* Variables and properties tooltips */ .variable-or-property > tooltip > label { @@ -546,7 +561,10 @@ text-decoration: line-through; } -.variable-or-property:not([safe-getter]) > tooltip > label[value=native-getter] { +.variable-or-property:not([safe-getter]) > tooltip > label[value=native-getter], +.variable-or-property:not([non-extensible]) > tooltip > label[value=non-extensible], +.variable-or-property:not([frozen]) > tooltip > label[value=frozen], +.variable-or-property:not([sealed]) > tooltip > label[value=sealed] { display: none; } diff --git a/toolkit/devtools/client/dbg-client.jsm b/toolkit/devtools/client/dbg-client.jsm index 53ee1c057c06..f0362b7a5f81 100644 --- a/toolkit/devtools/client/dbg-client.jsm +++ b/toolkit/devtools/client/dbg-client.jsm @@ -1437,6 +1437,10 @@ GripClient.prototype = { valid: true, + get isFrozen() this._grip.frozen, + get isSealed() this._grip.sealed, + get isExtensible() this._grip.extensible, + /** * Request the names of a function's formal parameters. * diff --git a/toolkit/devtools/server/actors/script.js b/toolkit/devtools/server/actors/script.js index e3831a06e7b4..eb26bb5b355b 100644 --- a/toolkit/devtools/server/actors/script.js +++ b/toolkit/devtools/server/actors/script.js @@ -1481,9 +1481,14 @@ ObjectActor.prototype = { * Returns a grip for this actor for returning in a protocol message. */ grip: function OA_grip() { - let g = { "type": "object", - "class": this.obj.class, - "actor": this.actorID }; + let g = { + "type": "object", + "class": this.obj.class, + "actor": this.actorID, + "extensible": this.obj.isExtensible(), + "frozen": this.obj.isFrozen(), + "sealed": this.obj.isSealed() + }; // Add additional properties for functions. if (this.obj.class === "Function") { @@ -1780,7 +1785,7 @@ ObjectActor.prototype.requestTypes = { /** - * Creates a pause-scoped actor for the specified object. + * Creates a pause-scoped actor for the specified object. * @see ObjectActor */ function PauseScopedObjectActor() diff --git a/toolkit/devtools/server/tests/unit/test_objectgrips-05.js b/toolkit/devtools/server/tests/unit/test_objectgrips-05.js new file mode 100644 index 000000000000..a203bb3374a5 --- /dev/null +++ b/toolkit/devtools/server/tests/unit/test_objectgrips-05.js @@ -0,0 +1,57 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * This test checks that frozen objects report themselves as frozen in their + * grip. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + gDebuggee.eval(function stopMe(arg1, arg2) { + debugger; + }.toString()); + + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect(function() { + attachTestTabAndResume(gClient, "test-grips", function(aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_object_grip(); + }); + }); + do_test_pending(); +} + +function test_object_grip() +{ + gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) { + let obj1 = aPacket.frame.arguments[0]; + do_check_true(obj1.frozen); + + let obj1Client = gThreadClient.pauseGrip(obj1); + do_check_true(obj1Client.isFrozen); + + let obj2 = aPacket.frame.arguments[1]; + do_check_false(obj2.frozen); + + let obj2Client = gThreadClient.pauseGrip(obj2); + do_check_false(obj2Client.isFrozen); + + gThreadClient.resume(_ => { + finishClient(gClient); + }); + }); + + gDebuggee.eval("(" + function () { + let obj1 = {}; + Object.freeze(obj1); + stopMe(obj1, {}); + } + "())"); +} + diff --git a/toolkit/devtools/server/tests/unit/test_objectgrips-06.js b/toolkit/devtools/server/tests/unit/test_objectgrips-06.js new file mode 100644 index 000000000000..3c214430b8c2 --- /dev/null +++ b/toolkit/devtools/server/tests/unit/test_objectgrips-06.js @@ -0,0 +1,57 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * This test checks that sealed objects report themselves as sealed in their + * grip. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + gDebuggee.eval(function stopMe(arg1, arg2) { + debugger; + }.toString()); + + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect(function() { + attachTestTabAndResume(gClient, "test-grips", function(aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_object_grip(); + }); + }); + do_test_pending(); +} + +function test_object_grip() +{ + gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) { + let obj1 = aPacket.frame.arguments[0]; + do_check_true(obj1.sealed); + + let obj1Client = gThreadClient.pauseGrip(obj1); + do_check_true(obj1Client.isSealed); + + let obj2 = aPacket.frame.arguments[1]; + do_check_false(obj2.sealed); + + let obj2Client = gThreadClient.pauseGrip(obj2); + do_check_false(obj2Client.isSealed); + + gThreadClient.resume(_ => { + finishClient(gClient); + }); + }); + + gDebuggee.eval("(" + function () { + let obj1 = {}; + Object.seal(obj1); + stopMe(obj1, {}); + } + "())"); +} + diff --git a/toolkit/devtools/server/tests/unit/test_objectgrips-07.js b/toolkit/devtools/server/tests/unit/test_objectgrips-07.js new file mode 100644 index 000000000000..10b2290b3bb0 --- /dev/null +++ b/toolkit/devtools/server/tests/unit/test_objectgrips-07.js @@ -0,0 +1,65 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * This test checks that objects which are not extensible report themselves as + * such. + */ + +var gDebuggee; +var gClient; +var gThreadClient; + +function run_test() +{ + initTestDebuggerServer(); + gDebuggee = addTestGlobal("test-grips"); + gDebuggee.eval(function stopMe(arg1, arg2, arg3, arg4) { + debugger; + }.toString()); + + gClient = new DebuggerClient(DebuggerServer.connectPipe()); + gClient.connect(function() { + attachTestTabAndResume(gClient, "test-grips", function(aResponse, aTabClient, aThreadClient) { + gThreadClient = aThreadClient; + test_object_grip(); + }); + }); + do_test_pending(); +} + +function test_object_grip() +{ + gThreadClient.addOneTimeListener("paused", function(aEvent, aPacket) { + let [f, s, ne, e] = aPacket.frame.arguments; + let [fClient, sClient, neClient, eClient] = aPacket.frame.arguments.map( + a => gThreadClient.pauseGrip(a)); + + do_check_false(f.extensible); + do_check_false(fClient.isExtensible); + + do_check_false(s.extensible); + do_check_false(sClient.isExtensible); + + do_check_false(ne.extensible); + do_check_false(neClient.isExtensible); + + do_check_true(e.extensible); + do_check_true(eClient.isExtensible); + + gThreadClient.resume(_ => { + finishClient(gClient); + }); + }); + + gDebuggee.eval("(" + function () { + let f = {}; + Object.freeze(f); + let s = {}; + Object.seal(s); + let ne = {}; + Object.preventExtensions(ne); + stopMe(f, s, ne, {}); + } + "())"); +} + diff --git a/toolkit/devtools/server/tests/unit/xpcshell.ini b/toolkit/devtools/server/tests/unit/xpcshell.ini index 21bc733df10d..da3a9ba48fce 100644 --- a/toolkit/devtools/server/tests/unit/xpcshell.ini +++ b/toolkit/devtools/server/tests/unit/xpcshell.ini @@ -95,6 +95,9 @@ reason = bug 820380 [test_objectgrips-02.js] [test_objectgrips-03.js] [test_objectgrips-04.js] +[test_objectgrips-05.js] +[test_objectgrips-06.js] +[test_objectgrips-07.js] [test_interrupt.js] [test_stepping-01.js] [test_stepping-02.js]