diff --git a/browser/devtools/debugger/debugger-view.js b/browser/devtools/debugger/debugger-view.js index c3d6abf8b74d..1e9790392021 100644 --- a/browser/devtools/debugger/debugger-view.js +++ b/browser/devtools/debugger/debugger-view.js @@ -1092,6 +1092,17 @@ DebuggerView.Scripts = { return script; }, + /** + * Returns the list of URIs for scripts in the page. + */ + scriptLocations: function DVS_scriptLocations() { + let locations = []; + for (let i = 0; i < this._scripts.itemCount; i++) { + locations.push(this._scripts.getItemAtIndex(i).value); + } + return locations; + }, + /** * The cached click listener for the scripts container. */ diff --git a/browser/devtools/webconsole/GcliCommands.jsm b/browser/devtools/webconsole/GcliCommands.jsm index 68ae84e8c9f9..31532b9e0ae5 100644 --- a/browser/devtools/webconsole/GcliCommands.jsm +++ b/browser/devtools/webconsole/GcliCommands.jsm @@ -125,3 +125,139 @@ gcli.addCommand({ document.defaultView.InspectorUI.openInspectorUI(args.node); } }); + +let breakpoints = []; + +/** + * 'break' command + */ +gcli.addCommand({ + name: "break", + description: gcli.lookup("breakDesc"), + manual: gcli.lookup("breakManual") +}); + + +/** + * 'break list' command + */ +gcli.addCommand({ + name: "break list", + description: gcli.lookup("breaklistDesc"), + returnType: "html", + exec: function(args, context) { + if (breakpoints.length === 0) { + return gcli.lookup("breaklistNone"); + } + + let reply = gcli.lookup("breaklistIntro"); + reply += "
    "; + breakpoints.forEach(function(breakpoint) { + let text = gcli.lookupFormat("breaklistLineEntry", + [breakpoint.file, breakpoint.line]); + reply += "
  1. " + text + "
  2. "; + }); + reply += "
"; + return reply; + } +}); + + +/** + * 'break add' command + */ +gcli.addCommand({ + name: "break add", + description: gcli.lookup("breakaddDesc"), + manual: gcli.lookup("breakaddManual") +}); + +/** + * 'break add line' command + */ +gcli.addCommand({ + name: "break add line", + description: gcli.lookup("breakaddlineDesc"), + params: [ + { + name: "file", + type: { + name: "selection", + data: function() { + let win = HUDService.currentContext(); + let dbg = win.DebuggerUI.getDebugger(win.gBrowser.selectedTab); + let files = []; + if (dbg) { + let scriptsView = dbg.frame.contentWindow.DebuggerView.Scripts; + for each (let script in scriptsView.scriptLocations()) { + files.push(script); + } + } + return files; + } + }, + description: gcli.lookup("breakaddlineFileDesc") + }, + { + name: "line", + type: { name: "number", min: 1, step: 10 }, + description: gcli.lookup("breakaddlineLineDesc") + } + ], + returnType: "html", + exec: function(args, context) { + args.type = "line"; + let win = HUDService.currentContext(); + let dbg = win.DebuggerUI.getDebugger(win.gBrowser.selectedTab); + if (!dbg) { + return gcli.lookup("breakaddDebuggerStopped"); + } + var promise = context.createPromise(); + let position = { url: args.file, line: args.line }; + dbg.activeThread.setBreakpoint(position, function(aResponse, aBpClient) { + if (aResponse.error) { + promise.resolve(gcli.lookupFormat("breakaddFailed", + [ aResponse.error ])); + return; + } + args.client = aBpClient; + breakpoints.push(args); + promise.resolve(gcli.lookup("breakaddAdded")); + }); + return promise; + } +}); + + +/** + * 'break del' command + */ +gcli.addCommand({ + name: "break del", + description: gcli.lookup("breakdelDesc"), + params: [ + { + name: "breakid", + type: { + name: "number", + min: 0, + max: function() { return breakpoints.length - 1; } + }, + description: gcli.lookup("breakdelBreakidDesc") + } + ], + returnType: "html", + exec: function(args, context) { + let breakpoint = breakpoints.splice(args.breakid, 1)[0]; + var promise = context.createPromise(); + try { + breakpoint.client.remove(function(aResponse) { + promise.resolve(gcli.lookup("breakdelRemoved")); + }); + } catch (ex) { + // If the debugger has been closed already, don't scare the user. + promise.resolve(gcli.lookup("breakdelRemoved")); + } + return promise; + } +}); diff --git a/browser/devtools/webconsole/test/Makefile.in b/browser/devtools/webconsole/test/Makefile.in index 39dfc61d0875..6c0b1d0c12d8 100644 --- a/browser/devtools/webconsole/test/Makefile.in +++ b/browser/devtools/webconsole/test/Makefile.in @@ -154,6 +154,7 @@ _BROWSER_TEST_FILES = \ browser_webconsole_bug_622303_persistent_filters.js \ browser_webconsole_window_zombie.js \ browser_cached_messages.js \ + browser_gcli_break.js \ head.js \ $(NULL) @@ -228,6 +229,7 @@ _BROWSER_TEST_PAGES = \ test-bug-658368-time-methods.html \ test-webconsole-error-observer.html \ test-for-of.html \ + browser_gcli_break.html \ $(NULL) libs:: $(_BROWSER_TEST_FILES) diff --git a/browser/devtools/webconsole/test/browser_gcli_break.html b/browser/devtools/webconsole/test/browser_gcli_break.html new file mode 100644 index 000000000000..fabbf1e3ec08 --- /dev/null +++ b/browser/devtools/webconsole/test/browser_gcli_break.html @@ -0,0 +1,18 @@ + + + + Browser GCLI break command test + + + + + + diff --git a/browser/devtools/webconsole/test/browser_gcli_break.js b/browser/devtools/webconsole/test/browser_gcli_break.js new file mode 100644 index 000000000000..c1192a2674ff --- /dev/null +++ b/browser/devtools/webconsole/test/browser_gcli_break.js @@ -0,0 +1,104 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// For more information on GCLI see: +// - https://github.com/mozilla/gcli/blob/master/docs/index.md +// - https://wiki.mozilla.org/DevTools/Features/GCLI + +// Tests that the break command works as it should + +let tempScope = {}; +Components.utils.import("resource:///modules/gcli.jsm", tempScope); +let gcli = tempScope.gcli; + +const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/browser_gcli_break.html"; +registerCleanupFunction(function() { + gcliterm = undefined; + requisition = undefined; + + Services.prefs.clearUserPref("devtools.gcli.enable"); +}); + +function test() { + Services.prefs.setBoolPref("devtools.gcli.enable", true); + addTab(TEST_URI); + browser.addEventListener("DOMContentLoaded", onLoad, false); +} + +let gcliterm; +let requisition; + +function onLoad() { + browser.removeEventListener("DOMContentLoaded", onLoad, false); + + try { + openConsole(); + + let hud = HUDService.getHudByWindow(content); + gcliterm = hud.gcliterm; + requisition = gcliterm.opts.requisition; + + testSetup(); + testCreateCommands(); + } + catch (ex) { + ok(false, "Caught exception: " + ex) + gcli._internal.console.error("Test Failure", ex); + closeConsole(); + finishTest(); + } +} + +function testSetup() { + ok(gcliterm, "We have a GCLI term"); + ok(requisition, "We have a Requisition"); +} + +function testCreateCommands() { + type("brea"); + is(gcliterm.completeNode.textContent, " break", "Completion for 'brea'"); + is(requisition.getStatus().toString(), "ERROR", "brea is ERROR"); + + type("break"); + is(requisition.getStatus().toString(), "ERROR", "break is ERROR"); + + type("break add"); + is(requisition.getStatus().toString(), "ERROR", "break add is ERROR"); + + type("break add line"); + is(requisition.getStatus().toString(), "ERROR", "break add line is ERROR"); + + let pane = DebuggerUI.toggleDebugger(); + pane.onConnected = function test_onConnected(aPane) { + // Wait for the initial resume. + aPane.debuggerWindow.gClient.addOneTimeListener("resumed", function() { + delete aPane.onConnected; + aPane.debuggerWindow.gClient.activeThread.addOneTimeListener("scriptsadded", function() { + type("break add line " + TEST_URI + " " + content.wrappedJSObject.line0); + is(requisition.getStatus().toString(), "VALID", "break add line is VALID"); + requisition.exec(); + + type("break list"); + is(requisition.getStatus().toString(), "VALID", "break list is VALID"); + requisition.exec(); + + aPane.debuggerWindow.gClient.activeThread.resume(function() { + type("break del 0"); + is(requisition.getStatus().toString(), "VALID", "break del 0 is VALID"); + requisition.exec(); + + closeConsole(); + finishTest(); + }); + }); + // Trigger newScript notifications using eval. + content.wrappedJSObject.firstCall(); + }); + } +} + +function type(command) { + gcliterm.inputNode.value = command.slice(0, -1); + gcliterm.inputNode.focus(); + EventUtils.synthesizeKey(command.slice(-1), {}); +} diff --git a/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties b/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties index ce59a4d8bdd4..bfa6a92669bf 100644 --- a/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties +++ b/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties @@ -56,6 +56,75 @@ inspectNodeDesc=CSS selector # on what it does. inspectNodeManual=A CSS selector for use with Document.querySelector which identifies a single element +# LOCALIZATION NOTE (breakDesc) A very short string used to describe the +# function of the break command. +breakDesc=Manage breakpoints + +# LOCALIZATION NOTE (breakManual) A longer description describing the +# set of commands that control breakpoints. +breakManual=Commands to list, add and remove breakpoints + +# LOCALIZATION NOTE (breaklistDesc) A very short string used to describe the +# function of the 'break list' command. +breaklistDesc=Display known breakpoints + +# LOCALIZATION NOTE (breaklistLineEntry) Used in the output of the 'break list' +# command to display a single line breakpoint. +# %1$S=script URL, %2$S=line number +breaklistLineEntry=Line breakpoint at %1$S:%2$S + +# LOCALIZATION NOTE (breaklistNone) Used in the output of the 'break list' +# command to explain that the list is empty. +breaklistNone=No breakpoints set + +# LOCALIZATION NOTE (breaklistIntro) Used in the output of the 'break list' +# command to preface the list contents. +breaklistIntro=The following breakpoints are set: + +# LOCALIZATION NOTE (breakaddAdded) Used in the output of the 'break add' +# command to explain that a breakpoint was added. +breakaddAdded=Added breakpoint + +# LOCALIZATION NOTE (breakaddFailed) Used in the output of the 'break add' +# command to explain that a breakpoint could not be added. +breakaddFailed=Could not set breakpoint: %S + +# LOCALIZATION NOTE (breakaddDesc) A very short string used to describe the +# function of the 'break add' command. +breakaddDesc=Add a breakpoint + +# LOCALIZATION NOTE (breakaddManual) A longer description describing the +# set of commands that are responsible for adding breakpoints. +breakaddManual=Breakpoint types supported: line + +# LOCALIZATION NOTE (breakaddDebuggerStopped) Used in the output of the +# 'break add' command to explain that the debugger must be opened first. +breakaddDebuggerStopped=The debugger must be opened before setting breakpoints + +# LOCALIZATION NOTE (breakaddlineDesc) A very short string used to describe the +# function of the 'break add line' command. +breakaddlineDesc=Add a line breakpoint + +# LOCALIZATION NOTE (breakaddlineFileDesc) A very short string used to describe +# the function of the file parameter in the 'break add line' command. +breakaddlineFileDesc=JS file URI + +# LOCALIZATION NOTE (breakaddlineLineDesc) A very short string used to describe +# the function of the line parameter in the 'break add line' command. +breakaddlineLineDesc=Line number + +# LOCALIZATION NOTE (breakdelDesc) A very short string used to describe the +# function of the 'break del' command. +breakdelDesc=Remove a breakpoint + +# LOCALIZATION NOTE (breakdelBreakidDesc) A very short string used to describe +# the function of the index parameter in the 'break del' command. +breakdelBreakidDesc=Index of breakpoint + +# LOCALIZATION NOTE (breakdelRemoved) Used in the output of the 'break del' +# command to explain that a breakpoint was removed. +breakdelRemoved=Breakpoint removed + # 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.