diff --git a/browser/devtools/debugger/debugger-controller.js b/browser/devtools/debugger/debugger-controller.js index 67ae3c9402f9..2e742074fb7f 100644 --- a/browser/devtools/debugger/debugger-controller.js +++ b/browser/devtools/debugger/debugger-controller.js @@ -423,6 +423,7 @@ function StackFrames() { this._onResumed = this._onResumed.bind(this); this._onFrames = this._onFrames.bind(this); this._onFramesCleared = this._onFramesCleared.bind(this); + this._onBlackBoxChange = this._onBlackBoxChange.bind(this); this._afterFramesCleared = this._afterFramesCleared.bind(this); this.evaluate = this.evaluate.bind(this); } @@ -447,6 +448,7 @@ StackFrames.prototype = { this.activeThread.addListener("resumed", this._onResumed); this.activeThread.addListener("framesadded", this._onFrames); this.activeThread.addListener("framescleared", this._onFramesCleared); + window.addEventListener("Debugger:BlackBoxChange", this._onBlackBoxChange, false); this._handleTabNavigation(); }, @@ -462,6 +464,7 @@ StackFrames.prototype = { this.activeThread.removeListener("resumed", this._onResumed); this.activeThread.removeListener("framesadded", this._onFrames); this.activeThread.removeListener("framescleared", this._onFramesCleared); + window.removeEventListener("Debugger:BlackBoxChange", this._onBlackBoxChange, false); }, /** @@ -594,12 +597,22 @@ StackFrames.prototype = { // Make sure all the previous stackframes are removed before re-adding them. DebuggerView.StackFrames.empty(); + let previousBlackBoxed = null; for (let frame of this.activeThread.cachedFrames) { - let { depth, where: { url, line } } = frame; + let { depth, where: { url, line }, isBlackBoxed } = frame; let frameLocation = NetworkHelper.convertToUnicode(unescape(url)); let frameTitle = StackFrameUtils.getFrameTitle(frame); - DebuggerView.StackFrames.addFrame(frameTitle, frameLocation, line, depth); + if (isBlackBoxed) { + if (previousBlackBoxed == url) { + continue; + } + previousBlackBoxed = url; + } else { + previousBlackBoxed = null; + } + + DebuggerView.StackFrames.addFrame(frameTitle, frameLocation, line, depth, isBlackBoxed); } if (this.currentFrame == null) { DebuggerView.StackFrames.selectedDepth = 0; @@ -626,6 +639,18 @@ StackFrames.prototype = { window.setTimeout(this._afterFramesCleared, FRAME_STEP_CLEAR_DELAY); }, + /** + * Handler for the debugger's BlackBoxChange notification. + */ + _onBlackBoxChange: function() { + if (this.activeThread.state == "paused") { + // We have to clear out the existing frames and refetch them to get their + // updated black boxed status. + this.activeThread._clearFrames(); + this.activeThread.fillFrames(CALL_STACK_PAGE_SIZE); + } + }, + /** * Called soon after the thread client's framescleared notification. */ @@ -1000,6 +1025,27 @@ SourceScripts.prototype = { window.dispatchEvent(document, "Debugger:AfterSourcesAdded"); }, + /** + * Set the black boxed status of the given source. + * + * @param Object aSource + * The source form. + * @param bool aBlackBoxFlag + * True to black box the source, false to un-black box it. + */ + blackBox: function(aSource, aBlackBoxFlag) { + const sourceClient = this.activeThread.source(aSource); + sourceClient[aBlackBoxFlag ? "blackBox" : "unblackBox"](function({ error, message }) { + if (error) { + let msg = "Could not toggle black boxing for " + + aSource.url + ": " + message; + dumpn(msg); + return void Cu.reportError(msg); + } + window.dispatchEvent(document, "Debugger:BlackBoxChange", sourceClient); + }); + }, + /** * Gets a specified source's text. * diff --git a/browser/devtools/debugger/debugger-panes.js b/browser/devtools/debugger/debugger-panes.js index 8f1c934f609a..29491fc1b8b5 100644 --- a/browser/devtools/debugger/debugger-panes.js +++ b/browser/devtools/debugger/debugger-panes.js @@ -18,6 +18,7 @@ function SourcesView() { this._onSourceSelect = this._onSourceSelect.bind(this); this._onSourceClick = this._onSourceClick.bind(this); this._onBreakpointRemoved = this._onBreakpointRemoved.bind(this); + this._onSourceCheck = this._onSourceCheck.bind(this); this._onBreakpointClick = this._onBreakpointClick.bind(this); this._onBreakpointCheckboxClick = this._onBreakpointCheckboxClick.bind(this); this._onConditionalPopupShowing = this._onConditionalPopupShowing.bind(this); @@ -34,9 +35,12 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, { initialize: function() { dumpn("Initializing the SourcesView"); - this.widget = new SideMenuWidget(document.getElementById("sources")); + this.widget = new SideMenuWidget(document.getElementById("sources"), { + showCheckboxes: true + }); this.emptyText = L10N.getStr("noSourcesText"); this.unavailableText = L10N.getStr("noMatchingSourcesText"); + this._blackBoxCheckboxTooltip = L10N.getStr("blackBoxCheckboxTooltip"); this._commandset = document.getElementById("debuggerCommands"); this._popupset = document.getElementById("debuggerPopupset"); @@ -48,6 +52,7 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, { window.addEventListener("Debugger:EditorUnloaded", this._onEditorUnload, false); this.widget.addEventListener("select", this._onSourceSelect, false); this.widget.addEventListener("click", this._onSourceClick, false); + this.widget.addEventListener("check", this._onSourceCheck, false); this._cbPanel.addEventListener("popupshowing", this._onConditionalPopupShowing, false); this._cbPanel.addEventListener("popupshown", this._onConditionalPopupShown, false); this._cbPanel.addEventListener("popuphiding", this._onConditionalPopupHiding, false); @@ -70,6 +75,7 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, { window.removeEventListener("Debugger:EditorUnloaded", this._onEditorUnload, false); this.widget.removeEventListener("select", this._onSourceSelect, false); this.widget.removeEventListener("click", this._onSourceClick, false); + this.widget.removeEventListener("check", this._onSourceCheck, false); this._cbPanel.removeEventListener("popupshowing", this._onConditionalPopupShowing, false); this._cbPanel.removeEventListener("popupshowing", this._onConditionalPopupShown, false); this._cbPanel.removeEventListener("popuphiding", this._onConditionalPopupHiding, false); @@ -109,6 +115,8 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, { this.push([label, url, group], { staged: aOptions.staged, /* stage the item to be appended later? */ attachment: { + checkboxState: !aSource.isBlackBoxed, + checkboxTooltip: this._blackBoxCheckboxTooltip, source: aSource } }); @@ -639,6 +647,14 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, { DebuggerView.Filtering.target = this; }, + /** + * The check listener for the sources container. + */ + _onSourceCheck: function({ detail: { checked }, target }) { + let item = this.getItemForElement(target); + DebuggerController.SourceScripts.blackBox(item.attachment.source, !checked); + }, + /** * The click listener for a breakpoint container. */ diff --git a/browser/devtools/debugger/debugger-toolbar.js b/browser/devtools/debugger/debugger-toolbar.js index 76d484f468af..6caccf6a8ae4 100644 --- a/browser/devtools/debugger/debugger-toolbar.js +++ b/browser/devtools/debugger/debugger-toolbar.js @@ -424,8 +424,10 @@ StackFramesView.prototype = Heritage.extend(WidgetMethods, { * The line number to be displayed in the list. * @param number aDepth * The frame depth specified by the debugger. + * @param boolean aIsBlackBoxed + * Whether or not the frame is black boxed. */ - addFrame: function(aFrameTitle, aSourceLocation, aLineNumber, aDepth) { + addFrame: function(aFrameTitle, aSourceLocation, aLineNumber, aDepth, aIsBlackBoxed) { // Create the element node and menu entry for the stack frame item. let frameView = this._createFrameView.apply(this, arguments); let menuEntry = this._createMenuEntry.apply(this, arguments); @@ -471,29 +473,35 @@ StackFramesView.prototype = Heritage.extend(WidgetMethods, { * The line number to be displayed in the list. * @param number aDepth * The frame depth specified by the debugger. + * @param boolean aIsBlackBoxed + * Whether or not the frame is black boxed. * @return nsIDOMNode * The stack frame view. */ - _createFrameView: function(aFrameTitle, aSourceLocation, aLineNumber, aDepth) { - let frameDetails = - SourceUtils.trimUrlLength( - SourceUtils.getSourceLabel(aSourceLocation), - STACK_FRAMES_SOURCE_URL_MAX_LENGTH, - STACK_FRAMES_SOURCE_URL_TRIM_SECTION) + SEARCH_LINE_FLAG + aLineNumber; - - let frameTitleNode = document.createElement("label"); - frameTitleNode.className = "plain dbg-stackframe-title breadcrumbs-widget-item-tag"; - frameTitleNode.setAttribute("value", aFrameTitle); - - let frameDetailsNode = document.createElement("label"); - frameDetailsNode.className = "plain dbg-stackframe-details breadcrumbs-widget-item-id"; - frameDetailsNode.setAttribute("value", frameDetails); - + _createFrameView: function(aFrameTitle, aSourceLocation, aLineNumber, aDepth, aIsBlackBoxed) { let container = document.createElement("hbox"); container.id = "stackframe-" + aDepth; container.className = "dbg-stackframe"; - container.appendChild(frameTitleNode); + let frameDetails = SourceUtils.trimUrlLength( + SourceUtils.getSourceLabel(aSourceLocation), + STACK_FRAMES_SOURCE_URL_MAX_LENGTH, + STACK_FRAMES_SOURCE_URL_TRIM_SECTION); + + if (aIsBlackBoxed) { + container.classList.add("dbg-stackframe-black-boxed"); + } else { + let frameTitleNode = document.createElement("label"); + frameTitleNode.className = "plain dbg-stackframe-title breadcrumbs-widget-item-tag"; + frameTitleNode.setAttribute("value", aFrameTitle); + container.appendChild(frameTitleNode); + + frameDetails += SEARCH_LINE_FLAG + aLineNumber; + } + + let frameDetailsNode = document.createElement("label"); + frameDetailsNode.className = "plain dbg-stackframe-details breadcrumbs-widget-item-id"; + frameDetailsNode.setAttribute("value", frameDetails); container.appendChild(frameDetailsNode); return container; @@ -510,10 +518,12 @@ StackFramesView.prototype = Heritage.extend(WidgetMethods, { * The line number to be displayed in the list. * @param number aDepth * The frame depth specified by the debugger. + * @param boolean aIsBlackBoxed + * Whether or not the frame is black boxed. * @return object * An object containing the stack frame command and menu item. */ - _createMenuEntry: function(aFrameTitle, aSourceLocation, aLineNumber, aDepth) { + _createMenuEntry: function(aFrameTitle, aSourceLocation, aLineNumber, aDepth, aIsBlackBoxed) { let frameDescription = SourceUtils.trimUrlLength( SourceUtils.getSourceLabel(aSourceLocation), diff --git a/browser/devtools/debugger/test/Makefile.in b/browser/devtools/debugger/test/Makefile.in index 1bac0850c439..d05e9a263dca 100644 --- a/browser/devtools/debugger/test/Makefile.in +++ b/browser/devtools/debugger/test/Makefile.in @@ -12,6 +12,10 @@ include $(DEPTH)/config/autoconf.mk MOCHITEST_BROWSER_TESTS = \ browser_dbg_aaa_run_first_leaktest.js \ + browser_dbg_blackboxing-01.js \ + browser_dbg_blackboxing-02.js \ + browser_dbg_blackboxing-03.js \ + browser_dbg_blackboxing-04.js \ browser_dbg_clean-exit.js \ browser_dbg_cmd.js \ browser_dbg_cmd_break.js \ @@ -112,6 +116,11 @@ MOCHITEST_BROWSER_TESTS = \ $(NULL) MOCHITEST_BROWSER_PAGES = \ + browser_dbg_blackboxing.html \ + blackboxing_blackboxme.js \ + blackboxing_one.js \ + blackboxing_two.js \ + blackboxing_three.js \ browser_dbg_cmd_break.html \ browser_dbg_cmd.html \ testactors.js \ diff --git a/browser/devtools/debugger/test/blackboxing_blackboxme.js b/browser/devtools/debugger/test/blackboxing_blackboxme.js new file mode 100644 index 000000000000..713b3d50d89d --- /dev/null +++ b/browser/devtools/debugger/test/blackboxing_blackboxme.js @@ -0,0 +1,9 @@ +function blackboxme(fn) { + (function one() { + (function two() { + (function three() { + fn(); + }()); + }()); + }()); +} diff --git a/browser/devtools/debugger/test/blackboxing_one.js b/browser/devtools/debugger/test/blackboxing_one.js new file mode 100644 index 000000000000..ca366e9166d8 --- /dev/null +++ b/browser/devtools/debugger/test/blackboxing_one.js @@ -0,0 +1 @@ +function one() { two(); } diff --git a/browser/devtools/debugger/test/blackboxing_three.js b/browser/devtools/debugger/test/blackboxing_three.js new file mode 100644 index 000000000000..466acf18f4c6 --- /dev/null +++ b/browser/devtools/debugger/test/blackboxing_three.js @@ -0,0 +1 @@ +function three() { doDebuggerStatement(); } diff --git a/browser/devtools/debugger/test/blackboxing_two.js b/browser/devtools/debugger/test/blackboxing_two.js new file mode 100644 index 000000000000..9931515fc6de --- /dev/null +++ b/browser/devtools/debugger/test/blackboxing_two.js @@ -0,0 +1 @@ +function two() { three(); } diff --git a/browser/devtools/debugger/test/browser_dbg_blackboxing-01.js b/browser/devtools/debugger/test/browser_dbg_blackboxing-01.js new file mode 100644 index 000000000000..e5bec3d87c0a --- /dev/null +++ b/browser/devtools/debugger/test/browser_dbg_blackboxing-01.js @@ -0,0 +1,76 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that if we black box a source and then refresh, it is still black boxed. + */ + +const TAB_URL = EXAMPLE_URL + "binary_search.html"; + +var gPane = null; +var gTab = null; +var gDebuggee = null; +var gDebugger = null; + +function test() +{ + let scriptShown = false; + let framesAdded = false; + let resumed = false; + let testStarted = false; + + debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) { + resumed = true; + gTab = aTab; + gDebuggee = aDebuggee; + gPane = aPane; + gDebugger = gPane.panelWin; + + testBlackBoxSource(); + }); +} + +function testBlackBoxSource() { + once(gDebugger, "Debugger:SourceShown", function () { + const checkbox = gDebugger.document.querySelector(".side-menu-widget-item-checkbox"); + ok(checkbox, "Should get the checkbox for black boxing the source"); + ok(checkbox.checked, "Should not be black boxed by default"); + + once(gDebugger, "Debugger:BlackBoxChange", function (event) { + const sourceClient = event.detail; + ok(sourceClient.isBlackBoxed, "The source should be black boxed now"); + ok(!checkbox.checked, "The checkbox should no longer be checked."); + + testBlackBoxReload(); + }); + + checkbox.click(); + }); +} + +function testBlackBoxReload() { + once(gDebugger, "Debugger:SourceShown", function () { + const checkbox = gDebugger.document.querySelector(".side-menu-widget-item-checkbox"); + ok(checkbox, "Should get the checkbox for black boxing the source"); + ok(!checkbox.checked, "Should still be black boxed"); + + closeDebuggerAndFinish(); + }); + + gDebuggee.location.reload(); +} + +function once(target, event, callback) { + target.addEventListener(event, function _listener(...args) { + target.removeEventListener(event, _listener, false); + callback.apply(null, args); + }, false); +} + +registerCleanupFunction(function() { + removeTab(gTab); + gPane = null; + gTab = null; + gDebuggee = null; + gDebugger = null; +}); diff --git a/browser/devtools/debugger/test/browser_dbg_blackboxing-02.js b/browser/devtools/debugger/test/browser_dbg_blackboxing-02.js new file mode 100644 index 000000000000..1be7d49115f3 --- /dev/null +++ b/browser/devtools/debugger/test/browser_dbg_blackboxing-02.js @@ -0,0 +1,87 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that black boxed frames are compressed into a single frame on the stack + * view. + */ + +const TAB_URL = EXAMPLE_URL + "browser_dbg_blackboxing.html"; +const BLACKBOXME_URL = EXAMPLE_URL + "blackboxing_blackboxme.js" + +var gPane = null; +var gTab = null; +var gDebuggee = null; +var gDebugger = null; + +function test() +{ + let scriptShown = false; + let framesAdded = false; + let resumed = false; + let testStarted = false; + + debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) { + resumed = true; + gTab = aTab; + gDebuggee = aDebuggee; + gPane = aPane; + gDebugger = gPane.panelWin; + + testBlackBoxSource(); + }); +} + +function testBlackBoxSource() { + once(gDebugger, "Debugger:SourceShown", function () { + const checkbox = getBlackBoxCheckbox(BLACKBOXME_URL); + ok(checkbox, "Should get the checkbox for blackBoxing the source"); + + once(gDebugger, "Debugger:BlackBoxChange", function (event) { + const sourceClient = event.detail; + ok(sourceClient.isBlackBoxed, "The source should be black boxed now"); + + testBlackBoxStack(); + }); + + checkbox.click(); + }); +} + +function testBlackBoxStack() { + const { activeThread } = gDebugger.DebuggerController; + activeThread.addOneTimeListener("framesadded", function () { + const frames = gDebugger.DebuggerView.StackFrames.widget._list; + + is(frames.querySelectorAll(".dbg-stackframe").length, 3, + "Should only get 3 frames"); + + is(frames.querySelectorAll(".dbg-stackframe-black-boxed").length, 1, + "And one of them should be the combined black boxed frames"); + + closeDebuggerAndFinish(); + }); + + gDebuggee.runTest(); +} + +function getBlackBoxCheckbox(url) { + return gDebugger.document.querySelector( + ".side-menu-widget-item[tooltiptext=\"" + + url + "\"] .side-menu-widget-item-checkbox"); +} + +function once(target, event, callback) { + target.addEventListener(event, function _listener(...args) { + target.removeEventListener(event, _listener, false); + callback.apply(null, args); + }, false); +} + +registerCleanupFunction(function() { + removeTab(gTab); + gPane = null; + gTab = null; + gDebuggee = null; + gDebugger = null; +}); diff --git a/browser/devtools/debugger/test/browser_dbg_blackboxing-03.js b/browser/devtools/debugger/test/browser_dbg_blackboxing-03.js new file mode 100644 index 000000000000..5f0cae2876ef --- /dev/null +++ b/browser/devtools/debugger/test/browser_dbg_blackboxing-03.js @@ -0,0 +1,96 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that black boxed frames are compressed into a single frame on the stack + * view when we are already paused. + */ + +const TAB_URL = EXAMPLE_URL + "browser_dbg_blackboxing.html"; +const BLACKBOXME_URL = EXAMPLE_URL + "blackboxing_blackboxme.js" + +var gPane = null; +var gTab = null; +var gDebuggee = null; +var gDebugger = null; + +function test() +{ + let scriptShown = false; + let framesAdded = false; + let resumed = false; + let testStarted = false; + + debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) { + resumed = true; + gTab = aTab; + gDebuggee = aDebuggee; + gPane = aPane; + gDebugger = gPane.panelWin; + + once(gDebugger, "Debugger:SourceShown", function () { + testBlackBoxStack(); + }); + }); +} + +function testBlackBoxStack() { + const { activeThread } = gDebugger.DebuggerController; + activeThread.addOneTimeListener("framesadded", function () { + const frames = gDebugger.DebuggerView.StackFrames.widget._list; + + is(frames.querySelectorAll(".dbg-stackframe").length, 6, + "Should get 6 frames"); + + is(frames.querySelectorAll(".dbg-stackframe-black-boxed").length, 0, + "And none of them are black boxed"); + + testBlackBoxSource(); + }); + + gDebuggee.runTest(); +} + +function testBlackBoxSource() { + const checkbox = getBlackBoxCheckbox(BLACKBOXME_URL); + ok(checkbox, "Should get the checkbox for black boxing the source"); + + once(gDebugger, "Debugger:BlackBoxChange", function (event) { + const { activeThread } = gDebugger.DebuggerController; + activeThread.addOneTimeListener("framesadded", function () { + const sourceClient = event.detail; + ok(sourceClient.isBlackBoxed, "The source should be black boxed now"); + + const frames = gDebugger.DebuggerView.StackFrames.widget._list; + is(frames.querySelectorAll(".dbg-stackframe").length, 3, + "Should only get 3 frames"); + is(frames.querySelectorAll(".dbg-stackframe-black-boxed").length, 1, + "And one of them is the combined black boxed frames"); + + closeDebuggerAndFinish(); + }); + }); + + checkbox.click(); +} + +function getBlackBoxCheckbox(url) { + return gDebugger.document.querySelector( + ".side-menu-widget-item[tooltiptext=\"" + + url + "\"] .side-menu-widget-item-checkbox"); +} + +function once(target, event, callback) { + target.addEventListener(event, function _listener(...args) { + target.removeEventListener(event, _listener, false); + callback.apply(null, args); + }, false); +} + +registerCleanupFunction(function() { + removeTab(gTab); + gPane = null; + gTab = null; + gDebuggee = null; + gDebugger = null; +}); diff --git a/browser/devtools/debugger/test/browser_dbg_blackboxing-04.js b/browser/devtools/debugger/test/browser_dbg_blackboxing-04.js new file mode 100644 index 000000000000..f09ce6ffa071 --- /dev/null +++ b/browser/devtools/debugger/test/browser_dbg_blackboxing-04.js @@ -0,0 +1,84 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we get a stack frame for each black boxed source, not a single one + * for all of them. + */ + +const TAB_URL = EXAMPLE_URL + "browser_dbg_blackboxing.html"; + +var gPane = null; +var gTab = null; +var gDebuggee = null; +var gDebugger = null; + +function test() +{ + let scriptShown = false; + let framesAdded = false; + let resumed = false; + let testStarted = false; + + debug_tab_pane(TAB_URL, function(aTab, aDebuggee, aPane) { + resumed = true; + gTab = aTab; + gDebuggee = aDebuggee; + gPane = aPane; + gDebugger = gPane.panelWin; + + once(gDebugger, "Debugger:SourceShown", function () { + blackBoxSources(); + }); + }); +} + +function blackBoxSources() { + let timesFired = 0; + gDebugger.addEventListener("Debugger:BlackBoxChange", function _onBlackboxChange() { + if (++timesFired !== 3) { + return; + } + gDebugger.removeEventListener("Debugger:BlackBoxChange", _onBlackboxChange, false); + + const { activeThread } = gDebugger.DebuggerController; + activeThread.addOneTimeListener("framesadded", testStackFrames); + + gDebuggee.one(); + }, false); + + getBlackBoxCheckbox(EXAMPLE_URL + "blackboxing_one.js").click(); + getBlackBoxCheckbox(EXAMPLE_URL + "blackboxing_two.js").click(); + getBlackBoxCheckbox(EXAMPLE_URL + "blackboxing_three.js").click(); +} + +function testStackFrames() { + const frames = gDebugger.DebuggerView.StackFrames.widget._list; + is(frames.querySelectorAll(".dbg-stackframe").length, 4, + "Should get 4 frames (one -> two -> three -> doDebuggerStatement)"); + is(frames.querySelectorAll(".dbg-stackframe-black-boxed").length, 3, + "And one, two, and three should each have their own black boxed frame."); + + closeDebuggerAndFinish(); +} + +function getBlackBoxCheckbox(url) { + return gDebugger.document.querySelector( + ".side-menu-widget-item[tooltiptext=\"" + + url + "\"] .side-menu-widget-item-checkbox"); +} + +function once(target, event, callback) { + target.addEventListener(event, function _listener(...args) { + target.removeEventListener(event, _listener, false); + callback.apply(null, args); + }, false); +} + +registerCleanupFunction(function() { + removeTab(gTab); + gPane = null; + gTab = null; + gDebuggee = null; + gDebugger = null; +}); diff --git a/browser/devtools/debugger/test/browser_dbg_blackboxing.html b/browser/devtools/debugger/test/browser_dbg_blackboxing.html new file mode 100644 index 000000000000..a4154b001525 --- /dev/null +++ b/browser/devtools/debugger/test/browser_dbg_blackboxing.html @@ -0,0 +1,23 @@ + + + + + Browser Debugger Blackbox Test + + + + + + + + + + diff --git a/browser/devtools/shared/widgets/SideMenuWidget.jsm b/browser/devtools/shared/widgets/SideMenuWidget.jsm index d4ca8d9f0d41..f24f0fe78226 100644 --- a/browser/devtools/shared/widgets/SideMenuWidget.jsm +++ b/browser/devtools/shared/widgets/SideMenuWidget.jsm @@ -25,21 +25,28 @@ this.EXPORTED_SYMBOLS = ["SideMenuWidget"]; * * @param nsIDOMNode aNode * The element associated with the widget. - * @param boolean aShowArrows - * Specifies if items in this container should display horizontal arrows. + * @param Object aOptions + * - showArrows: Specifies if items in this container should display + * horizontal arrows. + * - showCheckboxes: Specifies if items in this container should display + * checkboxes. */ -this.SideMenuWidget = function SideMenuWidget(aNode, aShowArrows = true) { +this.SideMenuWidget = function SideMenuWidget(aNode, aOptions={}) { this.document = aNode.ownerDocument; this.window = this.document.defaultView; this._parent = aNode; - this._showArrows = aShowArrows; + + let { showArrows, showCheckboxes } = aOptions; + this._showArrows = showArrows || false; + this._showCheckboxes = showCheckboxes || false; // Create an internal scrollbox container. this._list = this.document.createElement("scrollbox"); this._list.className = "side-menu-widget-container"; this._list.setAttribute("flex", "1"); this._list.setAttribute("orient", "vertical"); - this._list.setAttribute("with-arrow", aShowArrows); + this._list.setAttribute("with-arrow", showArrows); + this._list.setAttribute("with-checkboxes", showCheckboxes); this._list.setAttribute("tabindex", "0"); this._list.addEventListener("keypress", e => this.emit("keyPress", e), false); this._list.addEventListener("mousedown", e => this.emit("mousePress", e), false); @@ -91,10 +98,12 @@ SideMenuWidget.prototype = { * A tooltip attribute for the displayed item. * @param string aGroup [optional] * The group to place the displayed item into. + * @param Object aAttachment [optional] + * Extra data for the user. * @return nsIDOMNode * The element associated with the displayed item. */ - insertItemAt: function(aIndex, aContents, aTooltip = "", aGroup = "") { + insertItemAt: function(aIndex, aContents, aTooltip = "", aGroup = "", aAttachment={}) { aTooltip = NetworkHelper.convertToUnicode(unescape(aTooltip)); aGroup = NetworkHelper.convertToUnicode(unescape(aGroup)); @@ -115,7 +124,7 @@ SideMenuWidget.prototype = { (this._list.scrollTop + this._list.clientHeight >= this._list.scrollHeight); let group = this._getMenuGroupForName(aGroup); - let item = this._getMenuItemForGroup(group, aContents, aTooltip); + let item = this._getMenuItemForGroup(group, aContents, aTooltip, aAttachment); let element = item.insertSelfAt(aIndex); if (this.maintainSelectionVisible) { @@ -397,14 +406,17 @@ SideMenuWidget.prototype = { * The string or node displayed in the container. * @param string aTooltip [optional] * A tooltip attribute for the displayed item. + * @param object aAttachment [optional] + * The attachement object. */ - _getMenuItemForGroup: function(aGroup, aContents, aTooltip) { - return new SideMenuItem(aGroup, aContents, aTooltip, this._showArrows); + _getMenuItemForGroup: function(aGroup, aContents, aTooltip, aAttachment) { + return new SideMenuItem(aGroup, aContents, aTooltip, this._showArrows, this._showCheckboxes, aAttachment); }, window: null, document: null, _showArrows: false, + _showCheckboxes: false, _parent: null, _list: null, _boxObject: null, @@ -527,14 +539,30 @@ SideMenuGroup.prototype = { * The string or node displayed in the container. * @param boolean aArrowFlag * True if a horizontal arrow should be shown. + * @param boolean aCheckboxFlag + * True if a checkbox should be shown. + * @param object aAttachment [optional] + * The attachment object. */ -function SideMenuItem(aGroup, aContents, aTooltip, aArrowFlag) { +function SideMenuItem(aGroup, aContents, aTooltip, aArrowFlag, aCheckboxFlag, aAttachment={}) { this.document = aGroup.document; this.window = aGroup.window; this.ownerView = aGroup; - // Show a horizontal arrow towards the content. - if (aArrowFlag) { + let makeCheckbox = () => { + let checkbox = this.document.createElement("checkbox"); + checkbox.className = "side-menu-widget-item-checkbox"; + checkbox.setAttribute("checked", aAttachment.checkboxState); + checkbox.setAttribute("tooltiptext", aAttachment.checkboxTooltip); + checkbox.addEventListener("command", function () { + ViewHelpers.dispatchEvent(checkbox, "check", { + checked: checkbox.checked, + }); + }, false); + return checkbox; + }; + + if (aArrowFlag || aCheckboxFlag) { let container = this._container = this.document.createElement("hbox"); container.className = "side-menu-widget-item"; container.setAttribute("tooltiptext", aTooltip); @@ -542,13 +570,22 @@ function SideMenuItem(aGroup, aContents, aTooltip, aArrowFlag) { let target = this._target = this.document.createElement("vbox"); target.className = "side-menu-widget-item-contents"; - let arrow = this._arrow = this.document.createElement("hbox"); - arrow.className = "side-menu-widget-item-arrow"; + // Show a checkbox before the content. + if (aCheckboxFlag) { + let checkbox = this._checkbox = makeCheckbox(); + container.appendChild(checkbox); + } container.appendChild(target); - container.appendChild(arrow); + + // Show a horizontal arrow towards the content. + if (aArrowFlag) { + let arrow = this._arrow = this.document.createElement("hbox"); + arrow.className = "side-menu-widget-item-arrow"; + container.appendChild(arrow); + } } - // Skip a few redundant nodes when no horizontal arrow is shown. + // Skip a few redundant nodes when no horizontal arrow or checkbox is shown. else { let target = this._target = this._container = this.document.createElement("hbox"); target.className = "side-menu-widget-item side-menu-widget-item-contents"; diff --git a/browser/devtools/shared/widgets/ViewHelpers.jsm b/browser/devtools/shared/widgets/ViewHelpers.jsm index b6b65d010a20..609311abd43d 100644 --- a/browser/devtools/shared/widgets/ViewHelpers.jsm +++ b/browser/devtools/shared/widgets/ViewHelpers.jsm @@ -11,6 +11,7 @@ const Cu = Components.utils; const PANE_APPEARANCE_DELAY = 50; const PAGE_SIZE_ITEM_COUNT_RATIO = 5; +const WIDGET_FOCUSABLE_NODES = new Set(["vbox", "hbox"]); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); @@ -609,7 +610,7 @@ this.WidgetMethods = { * - relaxed: true if this container should allow dupes & degenerates * - attachment: some attached primitive/object for the item * - attributes: a batch of attributes set to the displayed element - * - finalize: function invoked when the item is removed + * - finalize: function invokde when the item is removed * @return Item * The item associated with the displayed element if an unstaged push, * undefined if the item was staged for a later commit. @@ -1117,16 +1118,21 @@ this.WidgetMethods = { _focusChange: function(aDirection) { let commandDispatcher = this._commandDispatcher; let prevFocusedElement = commandDispatcher.focusedElement; + let currFocusedElement; - commandDispatcher.suppressFocusScroll = true; - commandDispatcher[aDirection](); + do { + commandDispatcher.suppressFocusScroll = true; + commandDispatcher[aDirection](); + currFocusedElement = commandDispatcher.focusedElement; + + // Make sure the newly focused item is a part of this container. If the + // focus goes out of bounds, revert the previously focused item. + if (!this.getItemForElement(currFocusedElement)) { + prevFocusedElement.focus(); + return false; + } + } while (!WIDGET_FOCUSABLE_NODES.has(currFocusedElement.tagName)); - // Make sure the newly focused item is a part of this container. - // If the focus goes out of bounds, revert the previously focused item. - if (!this.getItemForElement(commandDispatcher.focusedElement)) { - prevFocusedElement.focus(); - return false; - } // Focus remained within bounds. return true; }, @@ -1208,7 +1214,10 @@ this.WidgetMethods = { */ getItemForElement: function(aElement) { while (aElement) { - let item = this._itemsByElement.get(aElement); + let item = + this._itemsByElement.get(aElement) || + this._itemsByElement.get(aElement.nextElementSibling) || + this._itemsByElement.get(aElement.previousElementSibling); if (item) { return item; } diff --git a/browser/locales/en-US/chrome/browser/devtools/debugger.properties b/browser/locales/en-US/chrome/browser/devtools/debugger.properties index f14430ed1ac8..5d6beb7caaaf 100644 --- a/browser/locales/en-US/chrome/browser/devtools/debugger.properties +++ b/browser/locales/en-US/chrome/browser/devtools/debugger.properties @@ -65,6 +65,11 @@ noMatchingGlobalsText=No matching globals # when there are no scripts. noSourcesText=This page has no sources. +# LOCALIZATION NOTE (blackBoxCheckboxTooltip) = The tooltip text to display when +# the user hovers over the checkbox used to toggle black boxing its associated +# source. +blackBoxCheckboxTooltip=Toggle black boxing + # LOCALIZATION NOTE (noMatchingSourcesText): The text to display in the # sources menu when there are no matching scripts after filtering. noMatchingSourcesText=No matching sources. diff --git a/browser/themes/linux/devtools/debugger.css b/browser/themes/linux/devtools/debugger.css index e10bc43d727a..95c040bcf75a 100644 --- a/browser/themes/linux/devtools/debugger.css +++ b/browser/themes/linux/devtools/debugger.css @@ -17,6 +17,27 @@ -moz-border-start-color: transparent; } +.side-menu-widget-item-checkbox { + -moz-appearance: none; + padding: 0; + margin: 0 -4px 0 4px; +} + +.side-menu-widget-item-checkbox > .checkbox-check { + -moz-appearance: none; + background: none; + background-image: url(itemToggle.png); + background-repeat: no-repeat; + background-clip: content-box; + background-position: -24px 0; + width: 24px; + height: 24px; +} + +.side-menu-widget-item-checkbox[checked] > .checkbox-check { + background-position: 0 0; +} + /* ListWidget items */ .list-widget-item { diff --git a/browser/themes/osx/devtools/debugger.css b/browser/themes/osx/devtools/debugger.css index 2748af514f37..90a82050c873 100644 --- a/browser/themes/osx/devtools/debugger.css +++ b/browser/themes/osx/devtools/debugger.css @@ -19,6 +19,27 @@ -moz-border-start-color: transparent; } +.side-menu-widget-item-checkbox { + -moz-appearance: none; + padding: 0; + margin: 0 -4px 0 4px; +} + +.side-menu-widget-item-checkbox > .checkbox-check { + -moz-appearance: none; + background: none; + background-image: url(itemToggle.png); + background-repeat: no-repeat; + background-clip: content-box; + background-position: -24px 0; + width: 24px; + height: 24px; +} + +.side-menu-widget-item-checkbox[checked] > .checkbox-check { + background-position: 0 0; +} + /* ListWidget items */ .list-widget-item { diff --git a/browser/themes/windows/devtools/debugger.css b/browser/themes/windows/devtools/debugger.css index 2784343b433b..885aac40f6c5 100644 --- a/browser/themes/windows/devtools/debugger.css +++ b/browser/themes/windows/devtools/debugger.css @@ -17,6 +17,27 @@ -moz-border-start-color: transparent; } +.side-menu-widget-item-checkbox { + -moz-appearance: none; + padding: 0; + margin: 0 -4px 0 4px; +} + +.side-menu-widget-item-checkbox > .checkbox-check { + -moz-appearance: none; + background: none; + background-image: url(itemToggle.png); + background-repeat: no-repeat; + background-clip: content-box; + background-position: -24px 0; + width: 24px; + height: 24px; +} + +.side-menu-widget-item-checkbox[checked] > .checkbox-check { + background-position: 0 0; +} + /* ListWidget items */ .list-widget-item {