diff --git a/browser/devtools/debugger/debugger-controller.js b/browser/devtools/debugger/debugger-controller.js index 67ae3c9402f..2e742074fb7 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 8f1c934f609..29491fc1b8b 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 c8b874d4691..cff4c0f6215 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 e93f892d07d..355baef5843 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 \ @@ -110,6 +114,11 @@ MOCHITEST_BROWSER_TESTS = \ $(NULL) MOCHITEST_BROWSER_PAGES = \ + browser_dbg_blackboxing.html \ + browser_dbg_blackboxing_blackboxme.js \ + browser_dbg_blackboxing_one.js \ + browser_dbg_blackboxing_two.js \ + browser_dbg_blackboxing_three.js \ browser_dbg_cmd_break.html \ browser_dbg_cmd.html \ testactors.js \ 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 00000000000..e5bec3d87c0 --- /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 00000000000..fb1c4dc7f8f --- /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 + "browser_dbg_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 00000000000..63f6fa1d59f --- /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 + "browser_dbg_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 00000000000..567ab969d07 --- /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 + "browser_dbg_blackboxing_one.js").click(); + getBlackBoxCheckbox(EXAMPLE_URL + "browser_dbg_blackboxing_two.js").click(); + getBlackBoxCheckbox(EXAMPLE_URL + "browser_dbg_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 00000000000..93c44fa4885 --- /dev/null +++ b/browser/devtools/debugger/test/browser_dbg_blackboxing.html @@ -0,0 +1,23 @@ + + +
+ +