michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: const { Cc, Ci, Cu } = require("chrome"); michael@0: const gcli = require("gcli/index"); michael@0: michael@0: loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm"); michael@0: michael@0: /** michael@0: * The commands and converters that are exported to GCLI michael@0: */ michael@0: exports.items = []; michael@0: michael@0: /** michael@0: * Utility to get access to the current breakpoint list. michael@0: * michael@0: * @param DebuggerPanel dbg michael@0: * The debugger panel. michael@0: * @return array michael@0: * An array of objects, one for each breakpoint, where each breakpoint michael@0: * object has the following properties: michael@0: * - url: the URL of the source file. michael@0: * - label: a unique string identifier designed to be user visible. michael@0: * - lineNumber: the line number of the breakpoint in the source file. michael@0: * - lineText: the text of the line at the breakpoint. michael@0: * - truncatedLineText: lineText truncated to MAX_LINE_TEXT_LENGTH. michael@0: */ michael@0: function getAllBreakpoints(dbg) { michael@0: let breakpoints = []; michael@0: let sources = dbg._view.Sources; michael@0: let { trimUrlLength: trim } = dbg.panelWin.SourceUtils; michael@0: michael@0: for (let source of sources) { michael@0: for (let { attachment: breakpoint } of source) { michael@0: breakpoints.push({ michael@0: url: source.value, michael@0: label: source.attachment.label + ":" + breakpoint.line, michael@0: lineNumber: breakpoint.line, michael@0: lineText: breakpoint.text, michael@0: truncatedLineText: trim(breakpoint.text, MAX_LINE_TEXT_LENGTH, "end") michael@0: }); michael@0: } michael@0: } michael@0: michael@0: return breakpoints; michael@0: } michael@0: michael@0: /** michael@0: * 'break' command michael@0: */ michael@0: exports.items.push({ michael@0: name: "break", michael@0: description: gcli.lookup("breakDesc"), michael@0: manual: gcli.lookup("breakManual") michael@0: }); michael@0: michael@0: /** michael@0: * 'break list' command michael@0: */ michael@0: exports.items.push({ michael@0: name: "break list", michael@0: description: gcli.lookup("breaklistDesc"), michael@0: returnType: "breakpoints", michael@0: exec: function(args, context) { michael@0: let dbg = getPanel(context, "jsdebugger", { ensureOpened: true }); michael@0: return dbg.then(getAllBreakpoints); michael@0: } michael@0: }); michael@0: michael@0: exports.items.push({ michael@0: item: "converter", michael@0: from: "breakpoints", michael@0: to: "view", michael@0: exec: function(breakpoints, context) { michael@0: let dbg = getPanel(context, "jsdebugger"); michael@0: if (dbg && breakpoints.length) { michael@0: return context.createView({ michael@0: html: breakListHtml, michael@0: data: { michael@0: breakpoints: breakpoints, michael@0: onclick: context.update, michael@0: ondblclick: context.updateExec michael@0: } michael@0: }); michael@0: } else { michael@0: return context.createView({ michael@0: html: "

${message}

", michael@0: data: { message: gcli.lookup("breaklistNone") } michael@0: }); michael@0: } michael@0: } michael@0: }); michael@0: michael@0: var breakListHtml = "" + michael@0: "" + michael@0: " " + michael@0: " " + michael@0: " " + michael@0: " " + michael@0: " " + michael@0: " " + michael@0: " " + michael@0: " " + michael@0: " " + michael@0: " " + michael@0: " " + michael@0: " " + michael@0: "
SourceLineActions
${breakpoint.label}" + michael@0: " ${breakpoint.truncatedLineText}" + michael@0: " " + michael@0: " " + michael@0: " " + gcli.lookup("breaklistOutRemove") + "" + michael@0: "
" + michael@0: ""; michael@0: michael@0: var MAX_LINE_TEXT_LENGTH = 30; michael@0: var MAX_LABEL_LENGTH = 20; michael@0: michael@0: /** michael@0: * 'break add' command michael@0: */ michael@0: exports.items.push({ michael@0: name: "break add", michael@0: description: gcli.lookup("breakaddDesc"), michael@0: manual: gcli.lookup("breakaddManual") michael@0: }); michael@0: michael@0: /** michael@0: * 'break add line' command michael@0: */ michael@0: exports.items.push({ michael@0: name: "break add line", michael@0: description: gcli.lookup("breakaddlineDesc"), michael@0: params: [ michael@0: { michael@0: name: "file", michael@0: type: { michael@0: name: "selection", michael@0: data: function(context) { michael@0: let dbg = getPanel(context, "jsdebugger"); michael@0: if (dbg) { michael@0: return dbg._view.Sources.values; michael@0: } michael@0: return []; michael@0: } michael@0: }, michael@0: description: gcli.lookup("breakaddlineFileDesc") michael@0: }, michael@0: { michael@0: name: "line", michael@0: type: { name: "number", min: 1, step: 10 }, michael@0: description: gcli.lookup("breakaddlineLineDesc") michael@0: } michael@0: ], michael@0: returnType: "string", michael@0: exec: function(args, context) { michael@0: let dbg = getPanel(context, "jsdebugger"); michael@0: if (!dbg) { michael@0: return gcli.lookup("debuggerStopped"); michael@0: } michael@0: michael@0: let deferred = context.defer(); michael@0: let position = { url: args.file, line: args.line }; michael@0: michael@0: dbg.addBreakpoint(position).then(() => { michael@0: deferred.resolve(gcli.lookup("breakaddAdded")); michael@0: }, aError => { michael@0: deferred.resolve(gcli.lookupFormat("breakaddFailed", [aError])); michael@0: }); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: }); michael@0: michael@0: /** michael@0: * 'break del' command michael@0: */ michael@0: exports.items.push({ michael@0: name: "break del", michael@0: description: gcli.lookup("breakdelDesc"), michael@0: params: [ michael@0: { michael@0: name: "breakpoint", michael@0: type: { michael@0: name: "selection", michael@0: lookup: function(context) { michael@0: let dbg = getPanel(context, "jsdebugger"); michael@0: if (!dbg) { michael@0: return []; michael@0: } michael@0: return getAllBreakpoints(dbg).map(breakpoint => ({ michael@0: name: breakpoint.label, michael@0: value: breakpoint, michael@0: description: breakpoint.truncatedLineText michael@0: })); michael@0: } michael@0: }, michael@0: description: gcli.lookup("breakdelBreakidDesc") michael@0: } michael@0: ], michael@0: returnType: "string", michael@0: exec: function(args, context) { michael@0: let dbg = getPanel(context, "jsdebugger"); michael@0: if (!dbg) { michael@0: return gcli.lookup("debuggerStopped"); michael@0: } michael@0: michael@0: let deferred = context.defer(); michael@0: let position = { url: args.breakpoint.url, line: args.breakpoint.lineNumber }; michael@0: michael@0: dbg.removeBreakpoint(position).then(() => { michael@0: deferred.resolve(gcli.lookup("breakdelRemoved")); michael@0: }, () => { michael@0: deferred.resolve(gcli.lookup("breakNotFound")); michael@0: }); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: }); michael@0: michael@0: /** michael@0: * 'dbg' command michael@0: */ michael@0: exports.items.push({ michael@0: name: "dbg", michael@0: description: gcli.lookup("dbgDesc"), michael@0: manual: gcli.lookup("dbgManual") michael@0: }); michael@0: michael@0: /** michael@0: * 'dbg open' command michael@0: */ michael@0: exports.items.push({ michael@0: name: "dbg open", michael@0: description: gcli.lookup("dbgOpen"), michael@0: params: [], michael@0: exec: function(args, context) { michael@0: let target = context.environment.target; michael@0: return gDevTools.showToolbox(target, "jsdebugger").then(() => null); michael@0: } michael@0: }); michael@0: michael@0: /** michael@0: * 'dbg close' command michael@0: */ michael@0: exports.items.push({ michael@0: name: "dbg close", michael@0: description: gcli.lookup("dbgClose"), michael@0: params: [], michael@0: exec: function(args, context) { michael@0: if (!getPanel(context, "jsdebugger")) { michael@0: return; michael@0: } michael@0: let target = context.environment.target; michael@0: return gDevTools.closeToolbox(target).then(() => null); michael@0: } michael@0: }); michael@0: michael@0: /** michael@0: * 'dbg interrupt' command michael@0: */ michael@0: exports.items.push({ michael@0: name: "dbg interrupt", michael@0: description: gcli.lookup("dbgInterrupt"), michael@0: params: [], michael@0: exec: function(args, context) { michael@0: let dbg = getPanel(context, "jsdebugger"); michael@0: if (!dbg) { michael@0: return gcli.lookup("debuggerStopped"); michael@0: } michael@0: michael@0: let controller = dbg._controller; michael@0: let thread = controller.activeThread; michael@0: if (!thread.paused) { michael@0: thread.interrupt(); michael@0: } michael@0: } michael@0: }); michael@0: michael@0: /** michael@0: * 'dbg continue' command michael@0: */ michael@0: exports.items.push({ michael@0: name: "dbg continue", michael@0: description: gcli.lookup("dbgContinue"), michael@0: params: [], michael@0: exec: function(args, context) { michael@0: let dbg = getPanel(context, "jsdebugger"); michael@0: if (!dbg) { michael@0: return gcli.lookup("debuggerStopped"); michael@0: } michael@0: michael@0: let controller = dbg._controller; michael@0: let thread = controller.activeThread; michael@0: if (thread.paused) { michael@0: thread.resume(); michael@0: } michael@0: } michael@0: }); michael@0: michael@0: /** michael@0: * 'dbg step' command michael@0: */ michael@0: exports.items.push({ michael@0: name: "dbg step", michael@0: description: gcli.lookup("dbgStepDesc"), michael@0: manual: gcli.lookup("dbgStepManual") michael@0: }); michael@0: michael@0: /** michael@0: * 'dbg step over' command michael@0: */ michael@0: exports.items.push({ michael@0: name: "dbg step over", michael@0: description: gcli.lookup("dbgStepOverDesc"), michael@0: params: [], michael@0: exec: function(args, context) { michael@0: let dbg = getPanel(context, "jsdebugger"); michael@0: if (!dbg) { michael@0: return gcli.lookup("debuggerStopped"); michael@0: } michael@0: michael@0: let controller = dbg._controller; michael@0: let thread = controller.activeThread; michael@0: if (thread.paused) { michael@0: thread.stepOver(); michael@0: } michael@0: } michael@0: }); michael@0: michael@0: /** michael@0: * 'dbg step in' command michael@0: */ michael@0: exports.items.push({ michael@0: name: 'dbg step in', michael@0: description: gcli.lookup("dbgStepInDesc"), michael@0: params: [], michael@0: exec: function(args, context) { michael@0: let dbg = getPanel(context, "jsdebugger"); michael@0: if (!dbg) { michael@0: return gcli.lookup("debuggerStopped"); michael@0: } michael@0: michael@0: let controller = dbg._controller; michael@0: let thread = controller.activeThread; michael@0: if (thread.paused) { michael@0: thread.stepIn(); michael@0: } michael@0: } michael@0: }); michael@0: michael@0: /** michael@0: * 'dbg step over' command michael@0: */ michael@0: exports.items.push({ michael@0: name: 'dbg step out', michael@0: description: gcli.lookup("dbgStepOutDesc"), michael@0: params: [], michael@0: exec: function(args, context) { michael@0: let dbg = getPanel(context, "jsdebugger"); michael@0: if (!dbg) { michael@0: return gcli.lookup("debuggerStopped"); michael@0: } michael@0: michael@0: let controller = dbg._controller; michael@0: let thread = controller.activeThread; michael@0: if (thread.paused) { michael@0: thread.stepOut(); michael@0: } michael@0: } michael@0: }); michael@0: michael@0: /** michael@0: * 'dbg list' command michael@0: */ michael@0: exports.items.push({ michael@0: name: "dbg list", michael@0: description: gcli.lookup("dbgListSourcesDesc"), michael@0: params: [], michael@0: returnType: "dom", michael@0: exec: function(args, context) { michael@0: let dbg = getPanel(context, "jsdebugger"); michael@0: if (!dbg) { michael@0: return gcli.lookup("debuggerClosed"); michael@0: } michael@0: michael@0: let sources = dbg._view.Sources.values; michael@0: let doc = context.environment.chromeDocument; michael@0: let div = createXHTMLElement(doc, "div"); michael@0: let ol = createXHTMLElement(doc, "ol"); michael@0: michael@0: sources.forEach(source => { michael@0: let li = createXHTMLElement(doc, "li"); michael@0: li.textContent = source; michael@0: ol.appendChild(li); michael@0: }); michael@0: div.appendChild(ol); michael@0: michael@0: return div; michael@0: } michael@0: }); michael@0: michael@0: /** michael@0: * Define the 'dbg blackbox' and 'dbg unblackbox' commands. michael@0: */ michael@0: [ michael@0: { michael@0: name: "blackbox", michael@0: clientMethod: "blackBox", michael@0: l10nPrefix: "dbgBlackBox" michael@0: }, michael@0: { michael@0: name: "unblackbox", michael@0: clientMethod: "unblackBox", michael@0: l10nPrefix: "dbgUnBlackBox" michael@0: } michael@0: ].forEach(function(cmd) { michael@0: const lookup = function(id) { michael@0: return gcli.lookup(cmd.l10nPrefix + id); michael@0: }; michael@0: michael@0: exports.items.push({ michael@0: name: "dbg " + cmd.name, michael@0: description: lookup("Desc"), michael@0: params: [ michael@0: { michael@0: name: "source", michael@0: type: { michael@0: name: "selection", michael@0: data: function(context) { michael@0: let dbg = getPanel(context, "jsdebugger"); michael@0: if (dbg) { michael@0: return dbg._view.Sources.values; michael@0: } michael@0: return []; michael@0: } michael@0: }, michael@0: description: lookup("SourceDesc"), michael@0: defaultValue: null michael@0: }, michael@0: { michael@0: name: "glob", michael@0: type: "string", michael@0: description: lookup("GlobDesc"), michael@0: defaultValue: null michael@0: }, michael@0: { michael@0: name: "invert", michael@0: type: "boolean", michael@0: description: lookup("InvertDesc") michael@0: } michael@0: ], michael@0: returnType: "dom", michael@0: exec: function(args, context) { michael@0: const dbg = getPanel(context, "jsdebugger"); michael@0: const doc = context.environment.chromeDocument; michael@0: if (!dbg) { michael@0: throw new Error(gcli.lookup("debuggerClosed")); michael@0: } michael@0: michael@0: const { promise, resolve, reject } = context.defer(); michael@0: const { activeThread } = dbg._controller; michael@0: const globRegExp = args.glob ? globToRegExp(args.glob) : null; michael@0: michael@0: // Filter the sources down to those that we will need to black box. michael@0: michael@0: function shouldBlackBox(source) { michael@0: var value = globRegExp && globRegExp.test(source.url) michael@0: || args.source && source.url == args.source; michael@0: return args.invert ? !value : value; michael@0: } michael@0: michael@0: const toBlackBox = [s.attachment.source michael@0: for (s of dbg._view.Sources.items) michael@0: if (shouldBlackBox(s.attachment.source))]; michael@0: michael@0: // If we aren't black boxing any sources, bail out now. michael@0: michael@0: if (toBlackBox.length === 0) { michael@0: const empty = createXHTMLElement(doc, "div"); michael@0: empty.textContent = lookup("EmptyDesc"); michael@0: return void resolve(empty); michael@0: } michael@0: michael@0: // Send the black box request to each source we are black boxing. As we michael@0: // get responses, accumulate the results in `blackBoxed`. michael@0: michael@0: const blackBoxed = []; michael@0: michael@0: for (let source of toBlackBox) { michael@0: activeThread.source(source)[cmd.clientMethod](function({ error }) { michael@0: if (error) { michael@0: blackBoxed.push(lookup("ErrorDesc") + " " + source.url); michael@0: } else { michael@0: blackBoxed.push(source.url); michael@0: } michael@0: michael@0: if (toBlackBox.length === blackBoxed.length) { michael@0: displayResults(); michael@0: } michael@0: }); michael@0: } michael@0: michael@0: // List the results for the user. michael@0: michael@0: function displayResults() { michael@0: const results = doc.createElement("div"); michael@0: results.textContent = lookup("NonEmptyDesc"); michael@0: michael@0: const list = createXHTMLElement(doc, "ul"); michael@0: results.appendChild(list); michael@0: michael@0: for (let result of blackBoxed) { michael@0: const item = createXHTMLElement(doc, "li"); michael@0: item.textContent = result; michael@0: list.appendChild(item); michael@0: } michael@0: resolve(results); michael@0: } michael@0: michael@0: return promise; michael@0: } michael@0: }); michael@0: }); michael@0: michael@0: /** michael@0: * A helper to create xhtml namespaced elements. michael@0: */ michael@0: function createXHTMLElement(document, tagname) { michael@0: return document.createElementNS("http://www.w3.org/1999/xhtml", tagname); michael@0: } michael@0: michael@0: /** michael@0: * A helper to go from a command context to a debugger panel. michael@0: */ michael@0: function getPanel(context, id, options = {}) { michael@0: if (!context) { michael@0: return undefined; michael@0: } michael@0: michael@0: let target = context.environment.target; michael@0: michael@0: if (options.ensureOpened) { michael@0: return gDevTools.showToolbox(target, id).then(toolbox => { michael@0: return toolbox.getPanel(id); michael@0: }); michael@0: } else { michael@0: let toolbox = gDevTools.getToolbox(target); michael@0: if (toolbox) { michael@0: return toolbox.getPanel(id); michael@0: } else { michael@0: return undefined; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Converts a glob to a regular expression. michael@0: */ michael@0: function globToRegExp(glob) { michael@0: const reStr = glob michael@0: // Escape existing regular expression syntax. michael@0: .replace(/\\/g, "\\\\") michael@0: .replace(/\//g, "\\/") michael@0: .replace(/\^/g, "\\^") michael@0: .replace(/\$/g, "\\$") michael@0: .replace(/\+/g, "\\+") michael@0: .replace(/\?/g, "\\?") michael@0: .replace(/\./g, "\\.") michael@0: .replace(/\(/g, "\\(") michael@0: .replace(/\)/g, "\\)") michael@0: .replace(/\=/g, "\\=") michael@0: .replace(/\!/g, "\\!") michael@0: .replace(/\|/g, "\\|") michael@0: .replace(/\{/g, "\\{") michael@0: .replace(/\}/g, "\\}") michael@0: .replace(/\,/g, "\\,") michael@0: .replace(/\[/g, "\\[") michael@0: .replace(/\]/g, "\\]") michael@0: .replace(/\-/g, "\\-") michael@0: // Turn * into the match everything wildcard. michael@0: .replace(/\*/g, ".*") michael@0: return new RegExp("^" + reStr + "$"); michael@0: }