browser/devtools/debugger/debugger-commands.js

Wed, 31 Dec 2014 06:55:46 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:46 +0100
changeset 1
ca08bd8f51b2
permissions
-rw-r--r--

Added tag TORBROWSER_REPLICA for changeset 6474c204b198

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 "use strict";
michael@0 6
michael@0 7 const { Cc, Ci, Cu } = require("chrome");
michael@0 8 const gcli = require("gcli/index");
michael@0 9
michael@0 10 loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
michael@0 11
michael@0 12 /**
michael@0 13 * The commands and converters that are exported to GCLI
michael@0 14 */
michael@0 15 exports.items = [];
michael@0 16
michael@0 17 /**
michael@0 18 * Utility to get access to the current breakpoint list.
michael@0 19 *
michael@0 20 * @param DebuggerPanel dbg
michael@0 21 * The debugger panel.
michael@0 22 * @return array
michael@0 23 * An array of objects, one for each breakpoint, where each breakpoint
michael@0 24 * object has the following properties:
michael@0 25 * - url: the URL of the source file.
michael@0 26 * - label: a unique string identifier designed to be user visible.
michael@0 27 * - lineNumber: the line number of the breakpoint in the source file.
michael@0 28 * - lineText: the text of the line at the breakpoint.
michael@0 29 * - truncatedLineText: lineText truncated to MAX_LINE_TEXT_LENGTH.
michael@0 30 */
michael@0 31 function getAllBreakpoints(dbg) {
michael@0 32 let breakpoints = [];
michael@0 33 let sources = dbg._view.Sources;
michael@0 34 let { trimUrlLength: trim } = dbg.panelWin.SourceUtils;
michael@0 35
michael@0 36 for (let source of sources) {
michael@0 37 for (let { attachment: breakpoint } of source) {
michael@0 38 breakpoints.push({
michael@0 39 url: source.value,
michael@0 40 label: source.attachment.label + ":" + breakpoint.line,
michael@0 41 lineNumber: breakpoint.line,
michael@0 42 lineText: breakpoint.text,
michael@0 43 truncatedLineText: trim(breakpoint.text, MAX_LINE_TEXT_LENGTH, "end")
michael@0 44 });
michael@0 45 }
michael@0 46 }
michael@0 47
michael@0 48 return breakpoints;
michael@0 49 }
michael@0 50
michael@0 51 /**
michael@0 52 * 'break' command
michael@0 53 */
michael@0 54 exports.items.push({
michael@0 55 name: "break",
michael@0 56 description: gcli.lookup("breakDesc"),
michael@0 57 manual: gcli.lookup("breakManual")
michael@0 58 });
michael@0 59
michael@0 60 /**
michael@0 61 * 'break list' command
michael@0 62 */
michael@0 63 exports.items.push({
michael@0 64 name: "break list",
michael@0 65 description: gcli.lookup("breaklistDesc"),
michael@0 66 returnType: "breakpoints",
michael@0 67 exec: function(args, context) {
michael@0 68 let dbg = getPanel(context, "jsdebugger", { ensureOpened: true });
michael@0 69 return dbg.then(getAllBreakpoints);
michael@0 70 }
michael@0 71 });
michael@0 72
michael@0 73 exports.items.push({
michael@0 74 item: "converter",
michael@0 75 from: "breakpoints",
michael@0 76 to: "view",
michael@0 77 exec: function(breakpoints, context) {
michael@0 78 let dbg = getPanel(context, "jsdebugger");
michael@0 79 if (dbg && breakpoints.length) {
michael@0 80 return context.createView({
michael@0 81 html: breakListHtml,
michael@0 82 data: {
michael@0 83 breakpoints: breakpoints,
michael@0 84 onclick: context.update,
michael@0 85 ondblclick: context.updateExec
michael@0 86 }
michael@0 87 });
michael@0 88 } else {
michael@0 89 return context.createView({
michael@0 90 html: "<p>${message}</p>",
michael@0 91 data: { message: gcli.lookup("breaklistNone") }
michael@0 92 });
michael@0 93 }
michael@0 94 }
michael@0 95 });
michael@0 96
michael@0 97 var breakListHtml = "" +
michael@0 98 "<table>" +
michael@0 99 " <thead>" +
michael@0 100 " <th>Source</th>" +
michael@0 101 " <th>Line</th>" +
michael@0 102 " <th>Actions</th>" +
michael@0 103 " </thead>" +
michael@0 104 " <tbody>" +
michael@0 105 " <tr foreach='breakpoint in ${breakpoints}'>" +
michael@0 106 " <td class='gcli-breakpoint-label'>${breakpoint.label}</td>" +
michael@0 107 " <td class='gcli-breakpoint-lineText'>" +
michael@0 108 " ${breakpoint.truncatedLineText}" +
michael@0 109 " </td>" +
michael@0 110 " <td>" +
michael@0 111 " <span class='gcli-out-shortcut'" +
michael@0 112 " data-command='break del ${breakpoint.label}'" +
michael@0 113 " onclick='${onclick}'" +
michael@0 114 " ondblclick='${ondblclick}'>" +
michael@0 115 " " + gcli.lookup("breaklistOutRemove") + "</span>" +
michael@0 116 " </td>" +
michael@0 117 " </tr>" +
michael@0 118 " </tbody>" +
michael@0 119 "</table>" +
michael@0 120 "";
michael@0 121
michael@0 122 var MAX_LINE_TEXT_LENGTH = 30;
michael@0 123 var MAX_LABEL_LENGTH = 20;
michael@0 124
michael@0 125 /**
michael@0 126 * 'break add' command
michael@0 127 */
michael@0 128 exports.items.push({
michael@0 129 name: "break add",
michael@0 130 description: gcli.lookup("breakaddDesc"),
michael@0 131 manual: gcli.lookup("breakaddManual")
michael@0 132 });
michael@0 133
michael@0 134 /**
michael@0 135 * 'break add line' command
michael@0 136 */
michael@0 137 exports.items.push({
michael@0 138 name: "break add line",
michael@0 139 description: gcli.lookup("breakaddlineDesc"),
michael@0 140 params: [
michael@0 141 {
michael@0 142 name: "file",
michael@0 143 type: {
michael@0 144 name: "selection",
michael@0 145 data: function(context) {
michael@0 146 let dbg = getPanel(context, "jsdebugger");
michael@0 147 if (dbg) {
michael@0 148 return dbg._view.Sources.values;
michael@0 149 }
michael@0 150 return [];
michael@0 151 }
michael@0 152 },
michael@0 153 description: gcli.lookup("breakaddlineFileDesc")
michael@0 154 },
michael@0 155 {
michael@0 156 name: "line",
michael@0 157 type: { name: "number", min: 1, step: 10 },
michael@0 158 description: gcli.lookup("breakaddlineLineDesc")
michael@0 159 }
michael@0 160 ],
michael@0 161 returnType: "string",
michael@0 162 exec: function(args, context) {
michael@0 163 let dbg = getPanel(context, "jsdebugger");
michael@0 164 if (!dbg) {
michael@0 165 return gcli.lookup("debuggerStopped");
michael@0 166 }
michael@0 167
michael@0 168 let deferred = context.defer();
michael@0 169 let position = { url: args.file, line: args.line };
michael@0 170
michael@0 171 dbg.addBreakpoint(position).then(() => {
michael@0 172 deferred.resolve(gcli.lookup("breakaddAdded"));
michael@0 173 }, aError => {
michael@0 174 deferred.resolve(gcli.lookupFormat("breakaddFailed", [aError]));
michael@0 175 });
michael@0 176
michael@0 177 return deferred.promise;
michael@0 178 }
michael@0 179 });
michael@0 180
michael@0 181 /**
michael@0 182 * 'break del' command
michael@0 183 */
michael@0 184 exports.items.push({
michael@0 185 name: "break del",
michael@0 186 description: gcli.lookup("breakdelDesc"),
michael@0 187 params: [
michael@0 188 {
michael@0 189 name: "breakpoint",
michael@0 190 type: {
michael@0 191 name: "selection",
michael@0 192 lookup: function(context) {
michael@0 193 let dbg = getPanel(context, "jsdebugger");
michael@0 194 if (!dbg) {
michael@0 195 return [];
michael@0 196 }
michael@0 197 return getAllBreakpoints(dbg).map(breakpoint => ({
michael@0 198 name: breakpoint.label,
michael@0 199 value: breakpoint,
michael@0 200 description: breakpoint.truncatedLineText
michael@0 201 }));
michael@0 202 }
michael@0 203 },
michael@0 204 description: gcli.lookup("breakdelBreakidDesc")
michael@0 205 }
michael@0 206 ],
michael@0 207 returnType: "string",
michael@0 208 exec: function(args, context) {
michael@0 209 let dbg = getPanel(context, "jsdebugger");
michael@0 210 if (!dbg) {
michael@0 211 return gcli.lookup("debuggerStopped");
michael@0 212 }
michael@0 213
michael@0 214 let deferred = context.defer();
michael@0 215 let position = { url: args.breakpoint.url, line: args.breakpoint.lineNumber };
michael@0 216
michael@0 217 dbg.removeBreakpoint(position).then(() => {
michael@0 218 deferred.resolve(gcli.lookup("breakdelRemoved"));
michael@0 219 }, () => {
michael@0 220 deferred.resolve(gcli.lookup("breakNotFound"));
michael@0 221 });
michael@0 222
michael@0 223 return deferred.promise;
michael@0 224 }
michael@0 225 });
michael@0 226
michael@0 227 /**
michael@0 228 * 'dbg' command
michael@0 229 */
michael@0 230 exports.items.push({
michael@0 231 name: "dbg",
michael@0 232 description: gcli.lookup("dbgDesc"),
michael@0 233 manual: gcli.lookup("dbgManual")
michael@0 234 });
michael@0 235
michael@0 236 /**
michael@0 237 * 'dbg open' command
michael@0 238 */
michael@0 239 exports.items.push({
michael@0 240 name: "dbg open",
michael@0 241 description: gcli.lookup("dbgOpen"),
michael@0 242 params: [],
michael@0 243 exec: function(args, context) {
michael@0 244 let target = context.environment.target;
michael@0 245 return gDevTools.showToolbox(target, "jsdebugger").then(() => null);
michael@0 246 }
michael@0 247 });
michael@0 248
michael@0 249 /**
michael@0 250 * 'dbg close' command
michael@0 251 */
michael@0 252 exports.items.push({
michael@0 253 name: "dbg close",
michael@0 254 description: gcli.lookup("dbgClose"),
michael@0 255 params: [],
michael@0 256 exec: function(args, context) {
michael@0 257 if (!getPanel(context, "jsdebugger")) {
michael@0 258 return;
michael@0 259 }
michael@0 260 let target = context.environment.target;
michael@0 261 return gDevTools.closeToolbox(target).then(() => null);
michael@0 262 }
michael@0 263 });
michael@0 264
michael@0 265 /**
michael@0 266 * 'dbg interrupt' command
michael@0 267 */
michael@0 268 exports.items.push({
michael@0 269 name: "dbg interrupt",
michael@0 270 description: gcli.lookup("dbgInterrupt"),
michael@0 271 params: [],
michael@0 272 exec: function(args, context) {
michael@0 273 let dbg = getPanel(context, "jsdebugger");
michael@0 274 if (!dbg) {
michael@0 275 return gcli.lookup("debuggerStopped");
michael@0 276 }
michael@0 277
michael@0 278 let controller = dbg._controller;
michael@0 279 let thread = controller.activeThread;
michael@0 280 if (!thread.paused) {
michael@0 281 thread.interrupt();
michael@0 282 }
michael@0 283 }
michael@0 284 });
michael@0 285
michael@0 286 /**
michael@0 287 * 'dbg continue' command
michael@0 288 */
michael@0 289 exports.items.push({
michael@0 290 name: "dbg continue",
michael@0 291 description: gcli.lookup("dbgContinue"),
michael@0 292 params: [],
michael@0 293 exec: function(args, context) {
michael@0 294 let dbg = getPanel(context, "jsdebugger");
michael@0 295 if (!dbg) {
michael@0 296 return gcli.lookup("debuggerStopped");
michael@0 297 }
michael@0 298
michael@0 299 let controller = dbg._controller;
michael@0 300 let thread = controller.activeThread;
michael@0 301 if (thread.paused) {
michael@0 302 thread.resume();
michael@0 303 }
michael@0 304 }
michael@0 305 });
michael@0 306
michael@0 307 /**
michael@0 308 * 'dbg step' command
michael@0 309 */
michael@0 310 exports.items.push({
michael@0 311 name: "dbg step",
michael@0 312 description: gcli.lookup("dbgStepDesc"),
michael@0 313 manual: gcli.lookup("dbgStepManual")
michael@0 314 });
michael@0 315
michael@0 316 /**
michael@0 317 * 'dbg step over' command
michael@0 318 */
michael@0 319 exports.items.push({
michael@0 320 name: "dbg step over",
michael@0 321 description: gcli.lookup("dbgStepOverDesc"),
michael@0 322 params: [],
michael@0 323 exec: function(args, context) {
michael@0 324 let dbg = getPanel(context, "jsdebugger");
michael@0 325 if (!dbg) {
michael@0 326 return gcli.lookup("debuggerStopped");
michael@0 327 }
michael@0 328
michael@0 329 let controller = dbg._controller;
michael@0 330 let thread = controller.activeThread;
michael@0 331 if (thread.paused) {
michael@0 332 thread.stepOver();
michael@0 333 }
michael@0 334 }
michael@0 335 });
michael@0 336
michael@0 337 /**
michael@0 338 * 'dbg step in' command
michael@0 339 */
michael@0 340 exports.items.push({
michael@0 341 name: 'dbg step in',
michael@0 342 description: gcli.lookup("dbgStepInDesc"),
michael@0 343 params: [],
michael@0 344 exec: function(args, context) {
michael@0 345 let dbg = getPanel(context, "jsdebugger");
michael@0 346 if (!dbg) {
michael@0 347 return gcli.lookup("debuggerStopped");
michael@0 348 }
michael@0 349
michael@0 350 let controller = dbg._controller;
michael@0 351 let thread = controller.activeThread;
michael@0 352 if (thread.paused) {
michael@0 353 thread.stepIn();
michael@0 354 }
michael@0 355 }
michael@0 356 });
michael@0 357
michael@0 358 /**
michael@0 359 * 'dbg step over' command
michael@0 360 */
michael@0 361 exports.items.push({
michael@0 362 name: 'dbg step out',
michael@0 363 description: gcli.lookup("dbgStepOutDesc"),
michael@0 364 params: [],
michael@0 365 exec: function(args, context) {
michael@0 366 let dbg = getPanel(context, "jsdebugger");
michael@0 367 if (!dbg) {
michael@0 368 return gcli.lookup("debuggerStopped");
michael@0 369 }
michael@0 370
michael@0 371 let controller = dbg._controller;
michael@0 372 let thread = controller.activeThread;
michael@0 373 if (thread.paused) {
michael@0 374 thread.stepOut();
michael@0 375 }
michael@0 376 }
michael@0 377 });
michael@0 378
michael@0 379 /**
michael@0 380 * 'dbg list' command
michael@0 381 */
michael@0 382 exports.items.push({
michael@0 383 name: "dbg list",
michael@0 384 description: gcli.lookup("dbgListSourcesDesc"),
michael@0 385 params: [],
michael@0 386 returnType: "dom",
michael@0 387 exec: function(args, context) {
michael@0 388 let dbg = getPanel(context, "jsdebugger");
michael@0 389 if (!dbg) {
michael@0 390 return gcli.lookup("debuggerClosed");
michael@0 391 }
michael@0 392
michael@0 393 let sources = dbg._view.Sources.values;
michael@0 394 let doc = context.environment.chromeDocument;
michael@0 395 let div = createXHTMLElement(doc, "div");
michael@0 396 let ol = createXHTMLElement(doc, "ol");
michael@0 397
michael@0 398 sources.forEach(source => {
michael@0 399 let li = createXHTMLElement(doc, "li");
michael@0 400 li.textContent = source;
michael@0 401 ol.appendChild(li);
michael@0 402 });
michael@0 403 div.appendChild(ol);
michael@0 404
michael@0 405 return div;
michael@0 406 }
michael@0 407 });
michael@0 408
michael@0 409 /**
michael@0 410 * Define the 'dbg blackbox' and 'dbg unblackbox' commands.
michael@0 411 */
michael@0 412 [
michael@0 413 {
michael@0 414 name: "blackbox",
michael@0 415 clientMethod: "blackBox",
michael@0 416 l10nPrefix: "dbgBlackBox"
michael@0 417 },
michael@0 418 {
michael@0 419 name: "unblackbox",
michael@0 420 clientMethod: "unblackBox",
michael@0 421 l10nPrefix: "dbgUnBlackBox"
michael@0 422 }
michael@0 423 ].forEach(function(cmd) {
michael@0 424 const lookup = function(id) {
michael@0 425 return gcli.lookup(cmd.l10nPrefix + id);
michael@0 426 };
michael@0 427
michael@0 428 exports.items.push({
michael@0 429 name: "dbg " + cmd.name,
michael@0 430 description: lookup("Desc"),
michael@0 431 params: [
michael@0 432 {
michael@0 433 name: "source",
michael@0 434 type: {
michael@0 435 name: "selection",
michael@0 436 data: function(context) {
michael@0 437 let dbg = getPanel(context, "jsdebugger");
michael@0 438 if (dbg) {
michael@0 439 return dbg._view.Sources.values;
michael@0 440 }
michael@0 441 return [];
michael@0 442 }
michael@0 443 },
michael@0 444 description: lookup("SourceDesc"),
michael@0 445 defaultValue: null
michael@0 446 },
michael@0 447 {
michael@0 448 name: "glob",
michael@0 449 type: "string",
michael@0 450 description: lookup("GlobDesc"),
michael@0 451 defaultValue: null
michael@0 452 },
michael@0 453 {
michael@0 454 name: "invert",
michael@0 455 type: "boolean",
michael@0 456 description: lookup("InvertDesc")
michael@0 457 }
michael@0 458 ],
michael@0 459 returnType: "dom",
michael@0 460 exec: function(args, context) {
michael@0 461 const dbg = getPanel(context, "jsdebugger");
michael@0 462 const doc = context.environment.chromeDocument;
michael@0 463 if (!dbg) {
michael@0 464 throw new Error(gcli.lookup("debuggerClosed"));
michael@0 465 }
michael@0 466
michael@0 467 const { promise, resolve, reject } = context.defer();
michael@0 468 const { activeThread } = dbg._controller;
michael@0 469 const globRegExp = args.glob ? globToRegExp(args.glob) : null;
michael@0 470
michael@0 471 // Filter the sources down to those that we will need to black box.
michael@0 472
michael@0 473 function shouldBlackBox(source) {
michael@0 474 var value = globRegExp && globRegExp.test(source.url)
michael@0 475 || args.source && source.url == args.source;
michael@0 476 return args.invert ? !value : value;
michael@0 477 }
michael@0 478
michael@0 479 const toBlackBox = [s.attachment.source
michael@0 480 for (s of dbg._view.Sources.items)
michael@0 481 if (shouldBlackBox(s.attachment.source))];
michael@0 482
michael@0 483 // If we aren't black boxing any sources, bail out now.
michael@0 484
michael@0 485 if (toBlackBox.length === 0) {
michael@0 486 const empty = createXHTMLElement(doc, "div");
michael@0 487 empty.textContent = lookup("EmptyDesc");
michael@0 488 return void resolve(empty);
michael@0 489 }
michael@0 490
michael@0 491 // Send the black box request to each source we are black boxing. As we
michael@0 492 // get responses, accumulate the results in `blackBoxed`.
michael@0 493
michael@0 494 const blackBoxed = [];
michael@0 495
michael@0 496 for (let source of toBlackBox) {
michael@0 497 activeThread.source(source)[cmd.clientMethod](function({ error }) {
michael@0 498 if (error) {
michael@0 499 blackBoxed.push(lookup("ErrorDesc") + " " + source.url);
michael@0 500 } else {
michael@0 501 blackBoxed.push(source.url);
michael@0 502 }
michael@0 503
michael@0 504 if (toBlackBox.length === blackBoxed.length) {
michael@0 505 displayResults();
michael@0 506 }
michael@0 507 });
michael@0 508 }
michael@0 509
michael@0 510 // List the results for the user.
michael@0 511
michael@0 512 function displayResults() {
michael@0 513 const results = doc.createElement("div");
michael@0 514 results.textContent = lookup("NonEmptyDesc");
michael@0 515
michael@0 516 const list = createXHTMLElement(doc, "ul");
michael@0 517 results.appendChild(list);
michael@0 518
michael@0 519 for (let result of blackBoxed) {
michael@0 520 const item = createXHTMLElement(doc, "li");
michael@0 521 item.textContent = result;
michael@0 522 list.appendChild(item);
michael@0 523 }
michael@0 524 resolve(results);
michael@0 525 }
michael@0 526
michael@0 527 return promise;
michael@0 528 }
michael@0 529 });
michael@0 530 });
michael@0 531
michael@0 532 /**
michael@0 533 * A helper to create xhtml namespaced elements.
michael@0 534 */
michael@0 535 function createXHTMLElement(document, tagname) {
michael@0 536 return document.createElementNS("http://www.w3.org/1999/xhtml", tagname);
michael@0 537 }
michael@0 538
michael@0 539 /**
michael@0 540 * A helper to go from a command context to a debugger panel.
michael@0 541 */
michael@0 542 function getPanel(context, id, options = {}) {
michael@0 543 if (!context) {
michael@0 544 return undefined;
michael@0 545 }
michael@0 546
michael@0 547 let target = context.environment.target;
michael@0 548
michael@0 549 if (options.ensureOpened) {
michael@0 550 return gDevTools.showToolbox(target, id).then(toolbox => {
michael@0 551 return toolbox.getPanel(id);
michael@0 552 });
michael@0 553 } else {
michael@0 554 let toolbox = gDevTools.getToolbox(target);
michael@0 555 if (toolbox) {
michael@0 556 return toolbox.getPanel(id);
michael@0 557 } else {
michael@0 558 return undefined;
michael@0 559 }
michael@0 560 }
michael@0 561 }
michael@0 562
michael@0 563 /**
michael@0 564 * Converts a glob to a regular expression.
michael@0 565 */
michael@0 566 function globToRegExp(glob) {
michael@0 567 const reStr = glob
michael@0 568 // Escape existing regular expression syntax.
michael@0 569 .replace(/\\/g, "\\\\")
michael@0 570 .replace(/\//g, "\\/")
michael@0 571 .replace(/\^/g, "\\^")
michael@0 572 .replace(/\$/g, "\\$")
michael@0 573 .replace(/\+/g, "\\+")
michael@0 574 .replace(/\?/g, "\\?")
michael@0 575 .replace(/\./g, "\\.")
michael@0 576 .replace(/\(/g, "\\(")
michael@0 577 .replace(/\)/g, "\\)")
michael@0 578 .replace(/\=/g, "\\=")
michael@0 579 .replace(/\!/g, "\\!")
michael@0 580 .replace(/\|/g, "\\|")
michael@0 581 .replace(/\{/g, "\\{")
michael@0 582 .replace(/\}/g, "\\}")
michael@0 583 .replace(/\,/g, "\\,")
michael@0 584 .replace(/\[/g, "\\[")
michael@0 585 .replace(/\]/g, "\\]")
michael@0 586 .replace(/\-/g, "\\-")
michael@0 587 // Turn * into the match everything wildcard.
michael@0 588 .replace(/\*/g, ".*")
michael@0 589 return new RegExp("^" + reStr + "$");
michael@0 590 }

mercurial