browser/devtools/debugger/debugger-commands.js

changeset 0
6474c204b198
     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 +}

mercurial