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

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

mercurial