1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/devtools/debugger/debugger-commands.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,590 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +const { Cc, Ci, Cu } = require("chrome"); 1.11 +const gcli = require("gcli/index"); 1.12 + 1.13 +loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm"); 1.14 + 1.15 +/** 1.16 + * The commands and converters that are exported to GCLI 1.17 + */ 1.18 +exports.items = []; 1.19 + 1.20 +/** 1.21 + * Utility to get access to the current breakpoint list. 1.22 + * 1.23 + * @param DebuggerPanel dbg 1.24 + * The debugger panel. 1.25 + * @return array 1.26 + * An array of objects, one for each breakpoint, where each breakpoint 1.27 + * object has the following properties: 1.28 + * - url: the URL of the source file. 1.29 + * - label: a unique string identifier designed to be user visible. 1.30 + * - lineNumber: the line number of the breakpoint in the source file. 1.31 + * - lineText: the text of the line at the breakpoint. 1.32 + * - truncatedLineText: lineText truncated to MAX_LINE_TEXT_LENGTH. 1.33 + */ 1.34 +function getAllBreakpoints(dbg) { 1.35 + let breakpoints = []; 1.36 + let sources = dbg._view.Sources; 1.37 + let { trimUrlLength: trim } = dbg.panelWin.SourceUtils; 1.38 + 1.39 + for (let source of sources) { 1.40 + for (let { attachment: breakpoint } of source) { 1.41 + breakpoints.push({ 1.42 + url: source.value, 1.43 + label: source.attachment.label + ":" + breakpoint.line, 1.44 + lineNumber: breakpoint.line, 1.45 + lineText: breakpoint.text, 1.46 + truncatedLineText: trim(breakpoint.text, MAX_LINE_TEXT_LENGTH, "end") 1.47 + }); 1.48 + } 1.49 + } 1.50 + 1.51 + return breakpoints; 1.52 +} 1.53 + 1.54 +/** 1.55 + * 'break' command 1.56 + */ 1.57 +exports.items.push({ 1.58 + name: "break", 1.59 + description: gcli.lookup("breakDesc"), 1.60 + manual: gcli.lookup("breakManual") 1.61 +}); 1.62 + 1.63 +/** 1.64 + * 'break list' command 1.65 + */ 1.66 +exports.items.push({ 1.67 + name: "break list", 1.68 + description: gcli.lookup("breaklistDesc"), 1.69 + returnType: "breakpoints", 1.70 + exec: function(args, context) { 1.71 + let dbg = getPanel(context, "jsdebugger", { ensureOpened: true }); 1.72 + return dbg.then(getAllBreakpoints); 1.73 + } 1.74 +}); 1.75 + 1.76 +exports.items.push({ 1.77 + item: "converter", 1.78 + from: "breakpoints", 1.79 + to: "view", 1.80 + exec: function(breakpoints, context) { 1.81 + let dbg = getPanel(context, "jsdebugger"); 1.82 + if (dbg && breakpoints.length) { 1.83 + return context.createView({ 1.84 + html: breakListHtml, 1.85 + data: { 1.86 + breakpoints: breakpoints, 1.87 + onclick: context.update, 1.88 + ondblclick: context.updateExec 1.89 + } 1.90 + }); 1.91 + } else { 1.92 + return context.createView({ 1.93 + html: "<p>${message}</p>", 1.94 + data: { message: gcli.lookup("breaklistNone") } 1.95 + }); 1.96 + } 1.97 + } 1.98 +}); 1.99 + 1.100 +var breakListHtml = "" + 1.101 + "<table>" + 1.102 + " <thead>" + 1.103 + " <th>Source</th>" + 1.104 + " <th>Line</th>" + 1.105 + " <th>Actions</th>" + 1.106 + " </thead>" + 1.107 + " <tbody>" + 1.108 + " <tr foreach='breakpoint in ${breakpoints}'>" + 1.109 + " <td class='gcli-breakpoint-label'>${breakpoint.label}</td>" + 1.110 + " <td class='gcli-breakpoint-lineText'>" + 1.111 + " ${breakpoint.truncatedLineText}" + 1.112 + " </td>" + 1.113 + " <td>" + 1.114 + " <span class='gcli-out-shortcut'" + 1.115 + " data-command='break del ${breakpoint.label}'" + 1.116 + " onclick='${onclick}'" + 1.117 + " ondblclick='${ondblclick}'>" + 1.118 + " " + gcli.lookup("breaklistOutRemove") + "</span>" + 1.119 + " </td>" + 1.120 + " </tr>" + 1.121 + " </tbody>" + 1.122 + "</table>" + 1.123 + ""; 1.124 + 1.125 +var MAX_LINE_TEXT_LENGTH = 30; 1.126 +var MAX_LABEL_LENGTH = 20; 1.127 + 1.128 +/** 1.129 + * 'break add' command 1.130 + */ 1.131 +exports.items.push({ 1.132 + name: "break add", 1.133 + description: gcli.lookup("breakaddDesc"), 1.134 + manual: gcli.lookup("breakaddManual") 1.135 +}); 1.136 + 1.137 +/** 1.138 + * 'break add line' command 1.139 + */ 1.140 +exports.items.push({ 1.141 + name: "break add line", 1.142 + description: gcli.lookup("breakaddlineDesc"), 1.143 + params: [ 1.144 + { 1.145 + name: "file", 1.146 + type: { 1.147 + name: "selection", 1.148 + data: function(context) { 1.149 + let dbg = getPanel(context, "jsdebugger"); 1.150 + if (dbg) { 1.151 + return dbg._view.Sources.values; 1.152 + } 1.153 + return []; 1.154 + } 1.155 + }, 1.156 + description: gcli.lookup("breakaddlineFileDesc") 1.157 + }, 1.158 + { 1.159 + name: "line", 1.160 + type: { name: "number", min: 1, step: 10 }, 1.161 + description: gcli.lookup("breakaddlineLineDesc") 1.162 + } 1.163 + ], 1.164 + returnType: "string", 1.165 + exec: function(args, context) { 1.166 + let dbg = getPanel(context, "jsdebugger"); 1.167 + if (!dbg) { 1.168 + return gcli.lookup("debuggerStopped"); 1.169 + } 1.170 + 1.171 + let deferred = context.defer(); 1.172 + let position = { url: args.file, line: args.line }; 1.173 + 1.174 + dbg.addBreakpoint(position).then(() => { 1.175 + deferred.resolve(gcli.lookup("breakaddAdded")); 1.176 + }, aError => { 1.177 + deferred.resolve(gcli.lookupFormat("breakaddFailed", [aError])); 1.178 + }); 1.179 + 1.180 + return deferred.promise; 1.181 + } 1.182 +}); 1.183 + 1.184 +/** 1.185 + * 'break del' command 1.186 + */ 1.187 +exports.items.push({ 1.188 + name: "break del", 1.189 + description: gcli.lookup("breakdelDesc"), 1.190 + params: [ 1.191 + { 1.192 + name: "breakpoint", 1.193 + type: { 1.194 + name: "selection", 1.195 + lookup: function(context) { 1.196 + let dbg = getPanel(context, "jsdebugger"); 1.197 + if (!dbg) { 1.198 + return []; 1.199 + } 1.200 + return getAllBreakpoints(dbg).map(breakpoint => ({ 1.201 + name: breakpoint.label, 1.202 + value: breakpoint, 1.203 + description: breakpoint.truncatedLineText 1.204 + })); 1.205 + } 1.206 + }, 1.207 + description: gcli.lookup("breakdelBreakidDesc") 1.208 + } 1.209 + ], 1.210 + returnType: "string", 1.211 + exec: function(args, context) { 1.212 + let dbg = getPanel(context, "jsdebugger"); 1.213 + if (!dbg) { 1.214 + return gcli.lookup("debuggerStopped"); 1.215 + } 1.216 + 1.217 + let deferred = context.defer(); 1.218 + let position = { url: args.breakpoint.url, line: args.breakpoint.lineNumber }; 1.219 + 1.220 + dbg.removeBreakpoint(position).then(() => { 1.221 + deferred.resolve(gcli.lookup("breakdelRemoved")); 1.222 + }, () => { 1.223 + deferred.resolve(gcli.lookup("breakNotFound")); 1.224 + }); 1.225 + 1.226 + return deferred.promise; 1.227 + } 1.228 +}); 1.229 + 1.230 +/** 1.231 + * 'dbg' command 1.232 + */ 1.233 +exports.items.push({ 1.234 + name: "dbg", 1.235 + description: gcli.lookup("dbgDesc"), 1.236 + manual: gcli.lookup("dbgManual") 1.237 +}); 1.238 + 1.239 +/** 1.240 + * 'dbg open' command 1.241 + */ 1.242 +exports.items.push({ 1.243 + name: "dbg open", 1.244 + description: gcli.lookup("dbgOpen"), 1.245 + params: [], 1.246 + exec: function(args, context) { 1.247 + let target = context.environment.target; 1.248 + return gDevTools.showToolbox(target, "jsdebugger").then(() => null); 1.249 + } 1.250 +}); 1.251 + 1.252 +/** 1.253 + * 'dbg close' command 1.254 + */ 1.255 +exports.items.push({ 1.256 + name: "dbg close", 1.257 + description: gcli.lookup("dbgClose"), 1.258 + params: [], 1.259 + exec: function(args, context) { 1.260 + if (!getPanel(context, "jsdebugger")) { 1.261 + return; 1.262 + } 1.263 + let target = context.environment.target; 1.264 + return gDevTools.closeToolbox(target).then(() => null); 1.265 + } 1.266 +}); 1.267 + 1.268 +/** 1.269 + * 'dbg interrupt' command 1.270 + */ 1.271 +exports.items.push({ 1.272 + name: "dbg interrupt", 1.273 + description: gcli.lookup("dbgInterrupt"), 1.274 + params: [], 1.275 + exec: function(args, context) { 1.276 + let dbg = getPanel(context, "jsdebugger"); 1.277 + if (!dbg) { 1.278 + return gcli.lookup("debuggerStopped"); 1.279 + } 1.280 + 1.281 + let controller = dbg._controller; 1.282 + let thread = controller.activeThread; 1.283 + if (!thread.paused) { 1.284 + thread.interrupt(); 1.285 + } 1.286 + } 1.287 +}); 1.288 + 1.289 +/** 1.290 + * 'dbg continue' command 1.291 + */ 1.292 +exports.items.push({ 1.293 + name: "dbg continue", 1.294 + description: gcli.lookup("dbgContinue"), 1.295 + params: [], 1.296 + exec: function(args, context) { 1.297 + let dbg = getPanel(context, "jsdebugger"); 1.298 + if (!dbg) { 1.299 + return gcli.lookup("debuggerStopped"); 1.300 + } 1.301 + 1.302 + let controller = dbg._controller; 1.303 + let thread = controller.activeThread; 1.304 + if (thread.paused) { 1.305 + thread.resume(); 1.306 + } 1.307 + } 1.308 +}); 1.309 + 1.310 +/** 1.311 + * 'dbg step' command 1.312 + */ 1.313 +exports.items.push({ 1.314 + name: "dbg step", 1.315 + description: gcli.lookup("dbgStepDesc"), 1.316 + manual: gcli.lookup("dbgStepManual") 1.317 +}); 1.318 + 1.319 +/** 1.320 + * 'dbg step over' command 1.321 + */ 1.322 +exports.items.push({ 1.323 + name: "dbg step over", 1.324 + description: gcli.lookup("dbgStepOverDesc"), 1.325 + params: [], 1.326 + exec: function(args, context) { 1.327 + let dbg = getPanel(context, "jsdebugger"); 1.328 + if (!dbg) { 1.329 + return gcli.lookup("debuggerStopped"); 1.330 + } 1.331 + 1.332 + let controller = dbg._controller; 1.333 + let thread = controller.activeThread; 1.334 + if (thread.paused) { 1.335 + thread.stepOver(); 1.336 + } 1.337 + } 1.338 +}); 1.339 + 1.340 +/** 1.341 + * 'dbg step in' command 1.342 + */ 1.343 +exports.items.push({ 1.344 + name: 'dbg step in', 1.345 + description: gcli.lookup("dbgStepInDesc"), 1.346 + params: [], 1.347 + exec: function(args, context) { 1.348 + let dbg = getPanel(context, "jsdebugger"); 1.349 + if (!dbg) { 1.350 + return gcli.lookup("debuggerStopped"); 1.351 + } 1.352 + 1.353 + let controller = dbg._controller; 1.354 + let thread = controller.activeThread; 1.355 + if (thread.paused) { 1.356 + thread.stepIn(); 1.357 + } 1.358 + } 1.359 +}); 1.360 + 1.361 +/** 1.362 + * 'dbg step over' command 1.363 + */ 1.364 +exports.items.push({ 1.365 + name: 'dbg step out', 1.366 + description: gcli.lookup("dbgStepOutDesc"), 1.367 + params: [], 1.368 + exec: function(args, context) { 1.369 + let dbg = getPanel(context, "jsdebugger"); 1.370 + if (!dbg) { 1.371 + return gcli.lookup("debuggerStopped"); 1.372 + } 1.373 + 1.374 + let controller = dbg._controller; 1.375 + let thread = controller.activeThread; 1.376 + if (thread.paused) { 1.377 + thread.stepOut(); 1.378 + } 1.379 + } 1.380 +}); 1.381 + 1.382 +/** 1.383 + * 'dbg list' command 1.384 + */ 1.385 +exports.items.push({ 1.386 + name: "dbg list", 1.387 + description: gcli.lookup("dbgListSourcesDesc"), 1.388 + params: [], 1.389 + returnType: "dom", 1.390 + exec: function(args, context) { 1.391 + let dbg = getPanel(context, "jsdebugger"); 1.392 + if (!dbg) { 1.393 + return gcli.lookup("debuggerClosed"); 1.394 + } 1.395 + 1.396 + let sources = dbg._view.Sources.values; 1.397 + let doc = context.environment.chromeDocument; 1.398 + let div = createXHTMLElement(doc, "div"); 1.399 + let ol = createXHTMLElement(doc, "ol"); 1.400 + 1.401 + sources.forEach(source => { 1.402 + let li = createXHTMLElement(doc, "li"); 1.403 + li.textContent = source; 1.404 + ol.appendChild(li); 1.405 + }); 1.406 + div.appendChild(ol); 1.407 + 1.408 + return div; 1.409 + } 1.410 +}); 1.411 + 1.412 +/** 1.413 + * Define the 'dbg blackbox' and 'dbg unblackbox' commands. 1.414 + */ 1.415 +[ 1.416 + { 1.417 + name: "blackbox", 1.418 + clientMethod: "blackBox", 1.419 + l10nPrefix: "dbgBlackBox" 1.420 + }, 1.421 + { 1.422 + name: "unblackbox", 1.423 + clientMethod: "unblackBox", 1.424 + l10nPrefix: "dbgUnBlackBox" 1.425 + } 1.426 +].forEach(function(cmd) { 1.427 + const lookup = function(id) { 1.428 + return gcli.lookup(cmd.l10nPrefix + id); 1.429 + }; 1.430 + 1.431 + exports.items.push({ 1.432 + name: "dbg " + cmd.name, 1.433 + description: lookup("Desc"), 1.434 + params: [ 1.435 + { 1.436 + name: "source", 1.437 + type: { 1.438 + name: "selection", 1.439 + data: function(context) { 1.440 + let dbg = getPanel(context, "jsdebugger"); 1.441 + if (dbg) { 1.442 + return dbg._view.Sources.values; 1.443 + } 1.444 + return []; 1.445 + } 1.446 + }, 1.447 + description: lookup("SourceDesc"), 1.448 + defaultValue: null 1.449 + }, 1.450 + { 1.451 + name: "glob", 1.452 + type: "string", 1.453 + description: lookup("GlobDesc"), 1.454 + defaultValue: null 1.455 + }, 1.456 + { 1.457 + name: "invert", 1.458 + type: "boolean", 1.459 + description: lookup("InvertDesc") 1.460 + } 1.461 + ], 1.462 + returnType: "dom", 1.463 + exec: function(args, context) { 1.464 + const dbg = getPanel(context, "jsdebugger"); 1.465 + const doc = context.environment.chromeDocument; 1.466 + if (!dbg) { 1.467 + throw new Error(gcli.lookup("debuggerClosed")); 1.468 + } 1.469 + 1.470 + const { promise, resolve, reject } = context.defer(); 1.471 + const { activeThread } = dbg._controller; 1.472 + const globRegExp = args.glob ? globToRegExp(args.glob) : null; 1.473 + 1.474 + // Filter the sources down to those that we will need to black box. 1.475 + 1.476 + function shouldBlackBox(source) { 1.477 + var value = globRegExp && globRegExp.test(source.url) 1.478 + || args.source && source.url == args.source; 1.479 + return args.invert ? !value : value; 1.480 + } 1.481 + 1.482 + const toBlackBox = [s.attachment.source 1.483 + for (s of dbg._view.Sources.items) 1.484 + if (shouldBlackBox(s.attachment.source))]; 1.485 + 1.486 + // If we aren't black boxing any sources, bail out now. 1.487 + 1.488 + if (toBlackBox.length === 0) { 1.489 + const empty = createXHTMLElement(doc, "div"); 1.490 + empty.textContent = lookup("EmptyDesc"); 1.491 + return void resolve(empty); 1.492 + } 1.493 + 1.494 + // Send the black box request to each source we are black boxing. As we 1.495 + // get responses, accumulate the results in `blackBoxed`. 1.496 + 1.497 + const blackBoxed = []; 1.498 + 1.499 + for (let source of toBlackBox) { 1.500 + activeThread.source(source)[cmd.clientMethod](function({ error }) { 1.501 + if (error) { 1.502 + blackBoxed.push(lookup("ErrorDesc") + " " + source.url); 1.503 + } else { 1.504 + blackBoxed.push(source.url); 1.505 + } 1.506 + 1.507 + if (toBlackBox.length === blackBoxed.length) { 1.508 + displayResults(); 1.509 + } 1.510 + }); 1.511 + } 1.512 + 1.513 + // List the results for the user. 1.514 + 1.515 + function displayResults() { 1.516 + const results = doc.createElement("div"); 1.517 + results.textContent = lookup("NonEmptyDesc"); 1.518 + 1.519 + const list = createXHTMLElement(doc, "ul"); 1.520 + results.appendChild(list); 1.521 + 1.522 + for (let result of blackBoxed) { 1.523 + const item = createXHTMLElement(doc, "li"); 1.524 + item.textContent = result; 1.525 + list.appendChild(item); 1.526 + } 1.527 + resolve(results); 1.528 + } 1.529 + 1.530 + return promise; 1.531 + } 1.532 + }); 1.533 +}); 1.534 + 1.535 +/** 1.536 + * A helper to create xhtml namespaced elements. 1.537 + */ 1.538 +function createXHTMLElement(document, tagname) { 1.539 + return document.createElementNS("http://www.w3.org/1999/xhtml", tagname); 1.540 +} 1.541 + 1.542 +/** 1.543 + * A helper to go from a command context to a debugger panel. 1.544 + */ 1.545 +function getPanel(context, id, options = {}) { 1.546 + if (!context) { 1.547 + return undefined; 1.548 + } 1.549 + 1.550 + let target = context.environment.target; 1.551 + 1.552 + if (options.ensureOpened) { 1.553 + return gDevTools.showToolbox(target, id).then(toolbox => { 1.554 + return toolbox.getPanel(id); 1.555 + }); 1.556 + } else { 1.557 + let toolbox = gDevTools.getToolbox(target); 1.558 + if (toolbox) { 1.559 + return toolbox.getPanel(id); 1.560 + } else { 1.561 + return undefined; 1.562 + } 1.563 + } 1.564 +} 1.565 + 1.566 +/** 1.567 + * Converts a glob to a regular expression. 1.568 + */ 1.569 +function globToRegExp(glob) { 1.570 + const reStr = glob 1.571 + // Escape existing regular expression syntax. 1.572 + .replace(/\\/g, "\\\\") 1.573 + .replace(/\//g, "\\/") 1.574 + .replace(/\^/g, "\\^") 1.575 + .replace(/\$/g, "\\$") 1.576 + .replace(/\+/g, "\\+") 1.577 + .replace(/\?/g, "\\?") 1.578 + .replace(/\./g, "\\.") 1.579 + .replace(/\(/g, "\\(") 1.580 + .replace(/\)/g, "\\)") 1.581 + .replace(/\=/g, "\\=") 1.582 + .replace(/\!/g, "\\!") 1.583 + .replace(/\|/g, "\\|") 1.584 + .replace(/\{/g, "\\{") 1.585 + .replace(/\}/g, "\\}") 1.586 + .replace(/\,/g, "\\,") 1.587 + .replace(/\[/g, "\\[") 1.588 + .replace(/\]/g, "\\]") 1.589 + .replace(/\-/g, "\\-") 1.590 + // Turn * into the match everything wildcard. 1.591 + .replace(/\*/g, ".*") 1.592 + return new RegExp("^" + reStr + "$"); 1.593 +}