From 14e226bf9ddd44f5ebb9ccf5b354f82c14f8ecde Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Sat, 27 Jul 2013 10:50:57 -0700 Subject: [PATCH] Bug 892605 - part 2: add 'dbg blackbox' and 'dbg unblackbox' gcli commands; r=vporof --- browser/devtools/debugger/CmdDebugger.jsm | 145 ++++++++++++++++++ .../devtools/debugger/debugger-controller.js | 13 ++ browser/devtools/debugger/test/Makefile.in | 1 + .../debugger/test/browser_dbg_cmd_blackbox.js | 134 ++++++++++++++++ .../browser/devtools/gclicommands.properties | 50 ++++++ toolkit/devtools/client/dbg-client.jsm | 1 + 6 files changed, 344 insertions(+) create mode 100644 browser/devtools/debugger/test/browser_dbg_cmd_blackbox.js diff --git a/browser/devtools/debugger/CmdDebugger.jsm b/browser/devtools/debugger/CmdDebugger.jsm index 397ecb39fbc..0986b8f4888 100644 --- a/browser/devtools/debugger/CmdDebugger.jsm +++ b/browser/devtools/debugger/CmdDebugger.jsm @@ -423,6 +423,122 @@ gcli.addCommand({ } }); +/** + * Define the 'dbg blackbox' and 'dbg unblackbox' commands. + */ +[ + { + name: "blackbox", + clientMethod: "blackBox", + l10nPrefix: "dbgBlackBox" + }, + { + name: "unblackbox", + clientMethod: "unblackBox", + l10nPrefix: "dbgUnBlackBox" + } +].forEach(function (cmd) { + const lookup = function (id) { + return gcli.lookup(cmd.l10nPrefix + id); + }; + + gcli.addCommand({ + name: "dbg " + cmd.name, + description: lookup("Desc"), + params: [ + { + name: "source", + type: { + name: "selection", + data: function (context) { + let dbg = getPanel(context, "jsdebugger"); + return dbg + ? [s for (s of dbg._view.Sources.values)] + : []; + } + }, + description: lookup("SourceDesc"), + defaultValue: null + }, + { + name: "glob", + type: "string", + description: lookup("GlobDesc"), + defaultValue: null + } + ], + returnType: "dom", + exec: function (args, context) { + const dbg = getPanel(context, "jsdebugger"); + const doc = context.environment.chromeDocument; + if (!dbg) { + throw new Error(gcli.lookup("debuggerClosed")); + } + + const { promise, resolve, reject } = context.defer(); + const { activeThread } = dbg._controller; + const globRegExp = args.glob + ? globToRegExp(args.glob) + : null; + + // Filter the sources down to those that we will need to black box. + + function shouldBlackBox(source) { + return globRegExp && globRegExp.test(source.url) + || args.source && source.url == args.source; + } + + const toBlackBox = [s.attachment.source + for (s of dbg._view.Sources.items) + if (shouldBlackBox(s.attachment.source))]; + + // If we aren't black boxing any sources, bail out now. + + if (toBlackBox.length === 0) { + const empty = createXHTMLElement(doc, "div"); + empty.textContent = lookup("EmptyDesc"); + return void resolve(empty); + } + + // Send the black box request to each source we are black boxing. As we + // get responses, accumulate the results in `blackBoxed`. + + const blackBoxed = []; + + for (let source of toBlackBox) { + activeThread.source(source)[cmd.clientMethod](function ({ error }) { + if (error) { + blackBoxed.push(lookup("ErrorDesc") + " " + source.url); + } else { + blackBoxed.push(source.url); + } + + if (toBlackBox.length === blackBoxed.length) { + displayResults(); + } + }); + } + + // List the results for the user. + + function displayResults() { + const results = doc.createElement("div"); + results.textContent = lookup("NonEmptyDesc"); + const list = createXHTMLElement(doc, "ul"); + results.appendChild(list); + for (let result of blackBoxed) { + const item = createXHTMLElement(doc, "li"); + item.textContent = result; + list.appendChild(item); + } + resolve(results); + } + + return promise; + } + }); +}); + /** * A helper to create xhtml namespaced elements */ @@ -452,3 +568,32 @@ function getPanel(context, id, options = {}) { } } } + +/** + * Converts a glob to a regular expression + */ +function globToRegExp(glob) { + const reStr = glob + // Escape existing regular expression syntax + .replace(/\\/g, "\\\\") + .replace(/\//g, "\\/") + .replace(/\^/g, "\\^") + .replace(/\$/g, "\\$") + .replace(/\+/g, "\\+") + .replace(/\?/g, "\\?") + .replace(/\./g, "\\.") + .replace(/\(/g, "\\(") + .replace(/\)/g, "\\)") + .replace(/\=/g, "\\=") + .replace(/\!/g, "\\!") + .replace(/\|/g, "\\|") + .replace(/\{/g, "\\{") + .replace(/\}/g, "\\}") + .replace(/\,/g, "\\,") + .replace(/\[/g, "\\[") + .replace(/\]/g, "\\]") + .replace(/\-/g, "\\-") + // Turn * into the match everything wildcard + .replace(/\*/g, ".*") + return new RegExp("^" + reStr + "$"); +} diff --git a/browser/devtools/debugger/debugger-controller.js b/browser/devtools/debugger/debugger-controller.js index 333daf3ccab..b8954af6fd8 100644 --- a/browser/devtools/debugger/debugger-controller.js +++ b/browser/devtools/debugger/debugger-controller.js @@ -890,6 +890,7 @@ function SourceScripts() { this._onNewGlobal = this._onNewGlobal.bind(this); this._onNewSource = this._onNewSource.bind(this); this._onSourcesAdded = this._onSourcesAdded.bind(this); + this._onBlackBoxChange = this._onBlackBoxChange.bind(this); } SourceScripts.prototype = { @@ -904,6 +905,7 @@ SourceScripts.prototype = { dumpn("SourceScripts is connecting..."); this.debuggerClient.addListener("newGlobal", this._onNewGlobal); this.debuggerClient.addListener("newSource", this._onNewSource); + this.activeThread.addListener("blackboxchange", this._onBlackBoxChange); this._handleTabNavigation(); }, @@ -918,6 +920,7 @@ SourceScripts.prototype = { window.clearTimeout(this._newSourceTimeout); this.debuggerClient.removeListener("newGlobal", this._onNewGlobal); this.debuggerClient.removeListener("newSource", this._onNewSource); + this.activeThread.removeListener("blackboxchange", this._onBlackBoxChange); }, /** @@ -1025,6 +1028,16 @@ SourceScripts.prototype = { window.dispatchEvent(document, "Debugger:AfterSourcesAdded"); }, + /** + * Handler for the debugger client's 'blackboxchange' notification. + */ + _onBlackBoxChange: function (aEvent, { url, isBlackBoxed }) { + const item = DebuggerView.Sources.getItemByValue(url); + if (item) { + DebuggerView.Sources.callMethod("checkItem", item.target, !isBlackBoxed); + } + }, + /** * Set the black boxed status of the given source. * diff --git a/browser/devtools/debugger/test/Makefile.in b/browser/devtools/debugger/test/Makefile.in index 5ad410a7ab0..a58a8f4095d 100644 --- a/browser/devtools/debugger/test/Makefile.in +++ b/browser/devtools/debugger/test/Makefile.in @@ -18,6 +18,7 @@ MOCHITEST_BROWSER_TESTS = \ browser_dbg_blackboxing-04.js \ browser_dbg_clean-exit.js \ browser_dbg_cmd.js \ + browser_dbg_cmd_blackbox.js \ browser_dbg_cmd_break.js \ browser_dbg_debuggerstatement.js \ browser_dbg_listtabs-01.js \ diff --git a/browser/devtools/debugger/test/browser_dbg_cmd_blackbox.js b/browser/devtools/debugger/test/browser_dbg_cmd_blackbox.js new file mode 100644 index 00000000000..8d704ad3a29 --- /dev/null +++ b/browser/devtools/debugger/test/browser_dbg_cmd_blackbox.js @@ -0,0 +1,134 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests that the 'dbg blackbox' and 'dbg unblackbox' commands work as they +// should. + +const TEST_URL = EXAMPLE_URL + "browser_dbg_blackboxing.html"; +const BLACKBOXME_URL = EXAMPLE_URL + "blackboxing_blackboxme.js"; +const BLACKBOXONE_URL = EXAMPLE_URL + "blackboxing_one.js"; +const BLACKBOXTWO_URL = EXAMPLE_URL + "blackboxing_two.js"; +const BLACKBOXTHREE_URL = EXAMPLE_URL + "blackboxing_three.js"; + +let gcli = Cu.import("resource://gre/modules/devtools/gcli.jsm", {}).gcli; + +let gTarget; +let gPanel; +let gOptions; +let gDebugger; +let gClient; +let gThreadClient; +let gTab; + +function cmd(typed, expectedNumEvents=1) { + const deferred = promise.defer(); + + let timesFired = 0; + gThreadClient.addListener("blackboxchange", function _onBlackBoxChange() { + if (++timesFired === expectedNumEvents) { + gThreadClient.removeListener("blackboxchange", _onBlackBoxChange); + deferred.resolve(); + } + }); + + helpers.audit(gOptions, [{ + setup: typed, + exec: {} + }]); + + return deferred.promise; +} + +function test() { + helpers.addTabWithToolbar(TEST_URL, function(options) { + gOptions = options; + gTarget = options.target; + return gDevTools.showToolbox(options.target, "jsdebugger") + .then(setupGlobals) + .then(waitForDebuggerSources) + .then(testBlackBoxSource) + .then(testUnBlackBoxSource) + .then(testBlackBoxGlob) + .then(testUnBlackBoxGlob) + .then(null, function (error) { + ok(false, "Got an error: " + error.message + "\n" + error.stack); + }) + .then(finishUp); + }); +} + +function setupGlobals(toolbox) { + gTab = gBrowser.selectedTab; + gPanel = toolbox.getCurrentPanel(); + gDebugger = gPanel.panelWin; + gClient = gDebugger.gClient; + gThreadClient = gClient.activeThread; +} + +function waitForDebuggerSources() { + const deferred = promise.defer(); + gDebugger.addEventListener("Debugger:SourceShown", function _onSourceShown() { + gDebugger.removeEventListener("Debugger:SourceShown", _onSourceShown, false); + deferred.resolve(); + }, false); + return deferred.promise; +} + +function testBlackBoxSource() { + return cmd("dbg blackbox " + BLACKBOXME_URL) + .then(function () { + const checkbox = getBlackBoxCheckbox(BLACKBOXME_URL); + ok(!checkbox.checked, + "Should be able to black box a specific source"); + }); +} + +function testUnBlackBoxSource() { + return cmd("dbg unblackbox " + BLACKBOXME_URL) + .then(function () { + const checkbox = getBlackBoxCheckbox(BLACKBOXME_URL); + ok(checkbox.checked, + "Should be able to stop black boxing a specific source"); + }); +} + +function testBlackBoxGlob() { + return cmd("dbg blackbox --glob *blackboxing_t*.js", 2) + .then(function () { + ok(getBlackBoxCheckbox(BLACKBOXME_URL).checked, + "blackboxme should not be black boxed because it doesn't match the glob"); + ok(getBlackBoxCheckbox(BLACKBOXONE_URL).checked, + "blackbox_one should not be black boxed because it doesn't match the glob"); + + ok(!getBlackBoxCheckbox(BLACKBOXTWO_URL).checked, + "blackbox_two should be black boxed because it matches the glob"); + ok(!getBlackBoxCheckbox(BLACKBOXTHREE_URL).checked, + "blackbox_three should be black boxed because it matches the glob"); + }); +} + +function testUnBlackBoxGlob() { + return cmd("dbg unblackbox --glob *blackboxing_t*.js", 2) + .then(function () { + ok(getBlackBoxCheckbox(BLACKBOXTWO_URL).checked, + "blackbox_two should be un-black boxed because it matches the glob"); + ok(getBlackBoxCheckbox(BLACKBOXTHREE_URL).checked, + "blackbox_three should be un-black boxed because it matches the glob"); + }); +} + +function finishUp() { + gTarget = null; + gPanel = null; + gOptions = null; + gClient = null; + gThreadClient = null; + gDebugger = null; + closeDebuggerAndFinish(); +} + +function getBlackBoxCheckbox(url) { + return gDebugger.document.querySelector( + ".side-menu-widget-item[tooltiptext=\"" + + url + "\"] .side-menu-widget-item-checkbox"); +} diff --git a/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties b/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties index a8290a48ada..74dc74f081c 100644 --- a/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties +++ b/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties @@ -415,6 +415,56 @@ dbgStepOutDesc=Steps out of the current function and up one level if the functio # function of the dbg list command. dbgListSourcesDesc=List the source URLs loaded in the debugger +# LOCALIZATION NOTE (dbgBlackBoxDesc) A very short string used to describe the +# function of the 'dbg blackbox' command. +dbgBlackBoxDesc=Black box sources in the debugger + +# LOCALIZATION NOTE (dbgBlackBoxSourceDesc) A very short string used to describe the +# 'source' parameter to the 'dbg blackbox' command. +dbgBlackBoxSourceDesc=A specific source to black box + +# LOCALIZATION NOTE (dbgBlackBoxGlobDesc) A very short string used to describe the +# 'glob' parameter to the 'dbg blackbox' command. +dbgBlackBoxGlobDesc=Black box all sources that match this glob (for example: "*.min.js") + +# LOCALIZATION NOTE (dbgBlackBoxEmptyDesc) A very short string used to let the +# user know that no sources were black boxed. +dbgBlackBoxEmptyDesc=(No sources black boxed) + +# LOCALIZATION NOTE (dbgBlackBoxNonEmptyDesc) A very short string used to let the +# user know which sources were black boxed. +dbgBlackBoxNonEmptyDesc=The following sources were black boxed: + +# LOCALIZATION NOTE (dbgBlackBoxErrorDesc) A very short string used to let the +# user know there was an error black boxing a source (whose url follows this +# text). +dbgBlackBoxErrorDesc=Error black boxing: + +# LOCALIZATION NOTE (dbgUnBlackBoxDesc) A very short string used to describe the +# function of the 'dbg unblackbox' command. +dbgUnBlackBoxDesc=Stop black boxing sources in the debugger + +# LOCALIZATION NOTE (dbgUnBlackBoxSourceDesc) A very short string used to describe the +# 'source' parameter to the 'dbg unblackbox' command. +dbgUnBlackBoxSourceDesc=A specific source to stop black boxing + +# LOCALIZATION NOTE (dbgUnBlackBoxGlobDesc) A very short string used to describe the +# 'glob' parameter to the 'dbg blackbox' command. +dbgUnBlackBoxGlobDesc=Stop black boxing all sources that match this glob (for example: "*.min.js") + +# LOCALIZATION NOTE (dbgUnBlackBoxEmptyDesc) A very short string used to let the +# user know that we did not stop black boxing any sources. +dbgUnBlackBoxEmptyDesc=(Did not stop black boxing any sources) + +# LOCALIZATION NOTE (dbgUnBlackBoxNonEmptyDesc) A very short string used to let the +# user know which sources we stopped black boxing. +dbgUnBlackBoxNonEmptyDesc=Stopped black boxing the following sources: + +# LOCALIZATION NOTE (dbgUnBlackBoxErrorDesc) A very short string used to let the +# user know there was an error black boxing a source (whose url follows this +# text). +dbgUnBlackBoxErrorDesc=Error stopping black boxing: + # LOCALIZATION NOTE (consolecloseDesc) A very short description of the # 'console close' command. This string is designed to be shown in a menu # alongside the command name, which is why it should be as short as possible. diff --git a/toolkit/devtools/client/dbg-client.jsm b/toolkit/devtools/client/dbg-client.jsm index 98943555217..f8f5c7edd7b 100644 --- a/toolkit/devtools/client/dbg-client.jsm +++ b/toolkit/devtools/client/dbg-client.jsm @@ -1761,6 +1761,7 @@ SourceClient.prototype = { get isBlackBoxed() this._isBlackBoxed, get actor() this._form.actor, get request() this._client.request, + get url() this._form.url, /** * Black box this SourceClient's source.