Bug 892605 - part 2: add 'dbg blackbox' and 'dbg unblackbox' gcli commands; r=vporof

This commit is contained in:
Nick Fitzgerald 2013-07-27 10:50:57 -07:00
parent 6443c688de
commit 14e226bf9d
6 changed files with 344 additions and 0 deletions

View File

@ -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 + "$");
}

View File

@ -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.
*

View File

@ -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 \

View File

@ -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");
}

View File

@ -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.

View File

@ -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.