123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633 |
- /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- "use strict";
- const { Cc, Ci, Cu } = require("chrome");
- const l10n = require("gcli/l10n");
- loader.lazyRequireGetter(this, "gDevTools",
- "devtools/client/framework/devtools", true);
- /**
- * The commands and converters that are exported to GCLI
- */
- exports.items = [];
- /**
- * Utility to get access to the current breakpoint list.
- *
- * @param DebuggerPanel dbg
- * The debugger panel.
- * @return array
- * An array of objects, one for each breakpoint, where each breakpoint
- * object has the following properties:
- * - url: the URL of the source file.
- * - label: a unique string identifier designed to be user visible.
- * - lineNumber: the line number of the breakpoint in the source file.
- * - lineText: the text of the line at the breakpoint.
- * - truncatedLineText: lineText truncated to MAX_LINE_TEXT_LENGTH.
- */
- function getAllBreakpoints(dbg) {
- let breakpoints = [];
- let sources = dbg._view.Sources;
- let { trimUrlLength: trim } = dbg.panelWin.SourceUtils;
- for (let source of sources) {
- for (let { attachment: breakpoint } of source) {
- breakpoints.push({
- url: source.attachment.source.url,
- label: source.attachment.label + ":" + breakpoint.line,
- lineNumber: breakpoint.line,
- lineText: breakpoint.text,
- truncatedLineText: trim(breakpoint.text, MAX_LINE_TEXT_LENGTH, "end")
- });
- }
- }
- return breakpoints;
- }
- function getAllSources(dbg) {
- if (!dbg) {
- return [];
- }
- let items = dbg._view.Sources.items;
- return items
- .filter(item => !!item.attachment.source.url)
- .map(item => ({
- name: item.attachment.source.url,
- value: item.attachment.source.actor
- }));
- }
- /**
- * 'break' command
- */
- exports.items.push({
- name: "break",
- description: l10n.lookup("breakDesc"),
- manual: l10n.lookup("breakManual")
- });
- /**
- * 'break list' command
- */
- exports.items.push({
- name: "break list",
- item: "command",
- runAt: "client",
- description: l10n.lookup("breaklistDesc"),
- returnType: "breakpoints",
- exec: function (args, context) {
- let dbg = getPanel(context, "jsdebugger", { ensureOpened: true });
- return dbg.then(getAllBreakpoints);
- }
- });
- exports.items.push({
- item: "converter",
- from: "breakpoints",
- to: "view",
- exec: function (breakpoints, context) {
- let dbg = getPanel(context, "jsdebugger");
- if (dbg && breakpoints.length) {
- return context.createView({
- html: breakListHtml,
- data: {
- breakpoints: breakpoints,
- onclick: context.update,
- ondblclick: context.updateExec
- }
- });
- } else {
- return context.createView({
- html: "<p>${message}</p>",
- data: { message: l10n.lookup("breaklistNone") }
- });
- }
- }
- });
- var breakListHtml = "" +
- "<table>" +
- " <thead>" +
- " <th>Source</th>" +
- " <th>Line</th>" +
- " <th>Actions</th>" +
- " </thead>" +
- " <tbody>" +
- " <tr foreach='breakpoint in ${breakpoints}'>" +
- " <td class='gcli-breakpoint-label'>${breakpoint.label}</td>" +
- " <td class='gcli-breakpoint-lineText'>" +
- " ${breakpoint.truncatedLineText}" +
- " </td>" +
- " <td>" +
- " <span class='gcli-out-shortcut'" +
- " data-command='break del ${breakpoint.label}'" +
- " onclick='${onclick}'" +
- " ondblclick='${ondblclick}'>" +
- " " + l10n.lookup("breaklistOutRemove") + "</span>" +
- " </td>" +
- " </tr>" +
- " </tbody>" +
- "</table>" +
- "";
- var MAX_LINE_TEXT_LENGTH = 30;
- var MAX_LABEL_LENGTH = 20;
- /**
- * 'break add' command
- */
- exports.items.push({
- name: "break add",
- description: l10n.lookup("breakaddDesc"),
- manual: l10n.lookup("breakaddManual")
- });
- /**
- * 'break add line' command
- */
- exports.items.push({
- item: "command",
- runAt: "client",
- name: "break add line",
- description: l10n.lookup("breakaddlineDesc"),
- params: [
- {
- name: "file",
- type: {
- name: "selection",
- lookup: function (context) {
- return getAllSources(getPanel(context, "jsdebugger"));
- }
- },
- description: l10n.lookup("breakaddlineFileDesc")
- },
- {
- name: "line",
- type: { name: "number", min: 1, step: 10 },
- description: l10n.lookup("breakaddlineLineDesc")
- }
- ],
- returnType: "string",
- exec: function (args, context) {
- let dbg = getPanel(context, "jsdebugger");
- if (!dbg) {
- return l10n.lookup("debuggerStopped");
- }
- let deferred = context.defer();
- let item = dbg._view.Sources.getItemForAttachment(a => {
- return a.source && a.source.actor === args.file;
- });
- let position = { actor: item.value, line: args.line };
- dbg.addBreakpoint(position).then(() => {
- deferred.resolve(l10n.lookup("breakaddAdded"));
- }, aError => {
- deferred.resolve(l10n.lookupFormat("breakaddFailed", [aError]));
- });
- return deferred.promise;
- }
- });
- /**
- * 'break del' command
- */
- exports.items.push({
- item: "command",
- runAt: "client",
- name: "break del",
- description: l10n.lookup("breakdelDesc"),
- params: [
- {
- name: "breakpoint",
- type: {
- name: "selection",
- lookup: function (context) {
- let dbg = getPanel(context, "jsdebugger");
- if (!dbg) {
- return [];
- }
- return getAllBreakpoints(dbg).map(breakpoint => ({
- name: breakpoint.label,
- value: breakpoint,
- description: breakpoint.truncatedLineText
- }));
- }
- },
- description: l10n.lookup("breakdelBreakidDesc")
- }
- ],
- returnType: "string",
- exec: function (args, context) {
- let dbg = getPanel(context, "jsdebugger");
- if (!dbg) {
- return l10n.lookup("debuggerStopped");
- }
- let source = dbg._view.Sources.getItemForAttachment(a => {
- return a.source && a.source.url === args.breakpoint.url;
- });
- let deferred = context.defer();
- let position = { actor: source.attachment.source.actor,
- line: args.breakpoint.lineNumber };
- dbg.removeBreakpoint(position).then(() => {
- deferred.resolve(l10n.lookup("breakdelRemoved"));
- }, () => {
- deferred.resolve(l10n.lookup("breakNotFound"));
- });
- return deferred.promise;
- }
- });
- /**
- * 'dbg' command
- */
- exports.items.push({
- name: "dbg",
- description: l10n.lookup("dbgDesc"),
- manual: l10n.lookup("dbgManual")
- });
- /**
- * 'dbg open' command
- */
- exports.items.push({
- item: "command",
- runAt: "client",
- name: "dbg open",
- description: l10n.lookup("dbgOpen"),
- params: [],
- exec: function (args, context) {
- let target = context.environment.target;
- return gDevTools.showToolbox(target, "jsdebugger").then(() => null);
- }
- });
- /**
- * 'dbg close' command
- */
- exports.items.push({
- item: "command",
- runAt: "client",
- name: "dbg close",
- description: l10n.lookup("dbgClose"),
- params: [],
- exec: function (args, context) {
- if (!getPanel(context, "jsdebugger")) {
- return;
- }
- let target = context.environment.target;
- return gDevTools.closeToolbox(target).then(() => null);
- }
- });
- /**
- * 'dbg interrupt' command
- */
- exports.items.push({
- item: "command",
- runAt: "client",
- name: "dbg interrupt",
- description: l10n.lookup("dbgInterrupt"),
- params: [],
- exec: function (args, context) {
- let dbg = getPanel(context, "jsdebugger");
- if (!dbg) {
- return l10n.lookup("debuggerStopped");
- }
- let controller = dbg._controller;
- let thread = controller.activeThread;
- if (!thread.paused) {
- thread.interrupt();
- }
- }
- });
- /**
- * 'dbg continue' command
- */
- exports.items.push({
- item: "command",
- runAt: "client",
- name: "dbg continue",
- description: l10n.lookup("dbgContinue"),
- params: [],
- exec: function (args, context) {
- let dbg = getPanel(context, "jsdebugger");
- if (!dbg) {
- return l10n.lookup("debuggerStopped");
- }
- let controller = dbg._controller;
- let thread = controller.activeThread;
- if (thread.paused) {
- thread.resume();
- }
- }
- });
- /**
- * 'dbg step' command
- */
- exports.items.push({
- item: "command",
- runAt: "client",
- name: "dbg step",
- description: l10n.lookup("dbgStepDesc"),
- manual: l10n.lookup("dbgStepManual")
- });
- /**
- * 'dbg step over' command
- */
- exports.items.push({
- item: "command",
- runAt: "client",
- name: "dbg step over",
- description: l10n.lookup("dbgStepOverDesc"),
- params: [],
- exec: function (args, context) {
- let dbg = getPanel(context, "jsdebugger");
- if (!dbg) {
- return l10n.lookup("debuggerStopped");
- }
- let controller = dbg._controller;
- let thread = controller.activeThread;
- if (thread.paused) {
- thread.stepOver();
- }
- }
- });
- /**
- * 'dbg step in' command
- */
- exports.items.push({
- item: "command",
- runAt: "client",
- name: "dbg step in",
- description: l10n.lookup("dbgStepInDesc"),
- params: [],
- exec: function (args, context) {
- let dbg = getPanel(context, "jsdebugger");
- if (!dbg) {
- return l10n.lookup("debuggerStopped");
- }
- let controller = dbg._controller;
- let thread = controller.activeThread;
- if (thread.paused) {
- thread.stepIn();
- }
- }
- });
- /**
- * 'dbg step over' command
- */
- exports.items.push({
- item: "command",
- runAt: "client",
- name: "dbg step out",
- description: l10n.lookup("dbgStepOutDesc"),
- params: [],
- exec: function (args, context) {
- let dbg = getPanel(context, "jsdebugger");
- if (!dbg) {
- return l10n.lookup("debuggerStopped");
- }
- let controller = dbg._controller;
- let thread = controller.activeThread;
- if (thread.paused) {
- thread.stepOut();
- }
- }
- });
- /**
- * 'dbg list' command
- */
- exports.items.push({
- item: "command",
- runAt: "client",
- name: "dbg list",
- description: l10n.lookup("dbgListSourcesDesc"),
- params: [],
- returnType: "dom",
- exec: function (args, context) {
- let dbg = getPanel(context, "jsdebugger");
- if (!dbg) {
- return l10n.lookup("debuggerClosed");
- }
- let sources = getAllSources(dbg);
- let doc = context.environment.chromeDocument;
- let div = createXHTMLElement(doc, "div");
- let ol = createXHTMLElement(doc, "ol");
- sources.forEach(source => {
- let li = createXHTMLElement(doc, "li");
- li.textContent = source.name;
- ol.appendChild(li);
- });
- div.appendChild(ol);
- return div;
- }
- });
- /**
- * Define the 'dbg blackbox' and 'dbg unblackbox' commands.
- */
- [
- {
- name: "blackbox",
- clientMethod: "blackBox",
- l10nPrefix: "dbgBlackBox"
- },
- {
- name: "unblackbox",
- clientMethod: "unblackBox",
- l10nPrefix: "dbgUnBlackBox"
- }
- ].forEach(function (cmd) {
- const lookup = function (id) {
- return l10n.lookup(cmd.l10nPrefix + id);
- };
- exports.items.push({
- item: "command",
- runAt: "client",
- name: "dbg " + cmd.name,
- description: lookup("Desc"),
- params: [
- {
- name: "source",
- type: {
- name: "selection",
- lookup: function (context) {
- return getAllSources(getPanel(context, "jsdebugger"));
- }
- },
- description: lookup("SourceDesc"),
- defaultValue: null
- },
- {
- name: "glob",
- type: "string",
- description: lookup("GlobDesc"),
- defaultValue: null
- },
- {
- name: "invert",
- type: "boolean",
- description: lookup("InvertDesc")
- }
- ],
- returnType: "dom",
- exec: function (args, context) {
- const dbg = getPanel(context, "jsdebugger");
- const doc = context.environment.chromeDocument;
- if (!dbg) {
- throw new Error(l10n.lookup("debuggerClosed"));
- }
- const { promise, resolve, reject } = context.defer();
- const { activeThread } = dbg._controller;
- const globRegExp = args.glob ? globToRegExp(args.glob) : null;
- // Filter the sources down to those that we will need to black box.
- function shouldBlackBox(source) {
- var value = globRegExp && globRegExp.test(source.url)
- || args.source && source.actor == args.source;
- return args.invert ? !value : value;
- }
- const toBlackBox = [];
- for (let {attachment: {source}} of dbg._view.Sources.items) {
- if (shouldBlackBox(source)) {
- toBlackBox.push(source);
- }
- }
- // If we aren't black boxing any sources, bail out now.
- if (toBlackBox.length === 0) {
- const empty = createXHTMLElement(doc, "div");
- empty.textContent = lookup("EmptyDesc");
- return void resolve(empty);
- }
- // Send the black box request to each source we are black boxing. As we
- // get responses, accumulate the results in `blackBoxed`.
- const blackBoxed = [];
- for (let source of toBlackBox) {
- dbg.blackbox(source, cmd.clientMethod === "blackBox").then(() => {
- blackBoxed.push(source.url);
- }, err => {
- blackBoxed.push(lookup("ErrorDesc") + " " + source.url);
- }).then(() => {
- if (toBlackBox.length === blackBoxed.length) {
- displayResults();
- }
- });
- }
- // List the results for the user.
- function displayResults() {
- const results = doc.createElement("div");
- results.textContent = lookup("NonEmptyDesc");
- const list = createXHTMLElement(doc, "ul");
- results.appendChild(list);
- for (let result of blackBoxed) {
- const item = createXHTMLElement(doc, "li");
- item.textContent = result;
- list.appendChild(item);
- }
- resolve(results);
- }
- return promise;
- }
- });
- });
- /**
- * A helper to create xhtml namespaced elements.
- */
- function createXHTMLElement(document, tagname) {
- return document.createElementNS("http://www.w3.org/1999/xhtml", tagname);
- }
- /**
- * A helper to go from a command context to a debugger panel.
- */
- function getPanel(context, id, options = {}) {
- if (!context) {
- return undefined;
- }
- let target = context.environment.target;
- if (options.ensureOpened) {
- return gDevTools.showToolbox(target, id).then(toolbox => {
- return toolbox.getPanel(id);
- });
- } else {
- let toolbox = gDevTools.getToolbox(target);
- if (toolbox) {
- return toolbox.getPanel(id);
- } else {
- return undefined;
- }
- }
- }
- /**
- * Converts a glob to a regular expression.
- */
- function globToRegExp(glob) {
- const reStr = glob
- // Escape existing regular expression syntax.
- .replace(/\\/g, "\\\\")
- .replace(/\//g, "\\/")
- .replace(/\^/g, "\\^")
- .replace(/\$/g, "\\$")
- .replace(/\+/g, "\\+")
- .replace(/\?/g, "\\?")
- .replace(/\./g, "\\.")
- .replace(/\(/g, "\\(")
- .replace(/\)/g, "\\)")
- .replace(/\=/g, "\\=")
- .replace(/\!/g, "\\!")
- .replace(/\|/g, "\\|")
- .replace(/\{/g, "\\{")
- .replace(/\}/g, "\\}")
- .replace(/\,/g, "\\,")
- .replace(/\[/g, "\\[")
- .replace(/\]/g, "\\]")
- .replace(/\-/g, "\\-")
- // Turn * into the match everything wildcard.
- .replace(/\*/g, ".*");
- return new RegExp("^" + reStr + "$");
- }
|