tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commands.js (14585B)


      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/>. */
      4 
      5 import { createFrame } from "./create";
      6 import { makeBreakpointServerLocationId } from "../../utils/breakpoint/index";
      7 
      8 import * as objectInspector from "resource://devtools/client/shared/components/object-inspector/index.js";
      9 
     10 let commands;
     11 let breakpoints;
     12 
     13 // The maximal number of stackframes to retrieve when pausing
     14 const CALL_STACK_PAGE_SIZE = 1000;
     15 
     16 function setupCommands(innerCommands) {
     17  commands = innerCommands;
     18  breakpoints = {};
     19 }
     20 
     21 function currentTarget() {
     22  return commands.targetCommand.targetFront;
     23 }
     24 
     25 function currentThreadFront() {
     26  return currentTarget().threadFront;
     27 }
     28 
     29 /**
     30 * Create an object front for the passed grip
     31 *
     32 * @param {object} grip
     33 * @param {object} frame: An optional frame that will manage the created object front.
     34 *                        if not passed, the current thread front will manage the object.
     35 * @returns {ObjectFront}
     36 */
     37 function createObjectFront(grip, frame) {
     38  if (!grip.actor) {
     39    throw new Error("Actor is missing");
     40  }
     41  const threadFront = frame?.thread
     42    ? lookupThreadFront(frame.thread)
     43    : currentThreadFront();
     44  const frameFront = frame ? threadFront.getActorByID(frame.id) : null;
     45  return commands.client.createObjectFront(grip, threadFront, frameFront);
     46 }
     47 
     48 async function loadObjectProperties(root, threadActorID) {
     49  const { utils } = objectInspector;
     50  const properties = await utils.loadProperties.loadItemProperties(
     51    root,
     52    commands.client,
     53    undefined,
     54    threadActorID
     55  );
     56  return utils.node.getChildren({
     57    item: root,
     58    loadedProperties: new Map([[root.path, properties]]),
     59  });
     60 }
     61 
     62 function releaseActor(actor) {
     63  if (!actor) {
     64    return Promise.resolve();
     65  }
     66  const objFront = commands.client.getFrontByID(actor);
     67 
     68  if (!objFront) {
     69    return Promise.resolve();
     70  }
     71 
     72  return objFront.release().catch(() => {});
     73 }
     74 
     75 function lookupTarget(thread) {
     76  if (thread == currentThreadFront().actor) {
     77    return currentTarget();
     78  }
     79 
     80  const targets = commands.targetCommand.getAllTargets(
     81    commands.targetCommand.ALL_TYPES
     82  );
     83  return targets.find(target => target.targetForm.threadActor == thread);
     84 }
     85 
     86 function lookupThreadFront(thread) {
     87  const target = lookupTarget(thread);
     88  return target.threadFront;
     89 }
     90 
     91 function listThreadFronts() {
     92  const targets = commands.targetCommand.getAllTargets(
     93    commands.targetCommand.ALL_TYPES
     94  );
     95  return targets.map(target => target.threadFront).filter(front => !!front);
     96 }
     97 
     98 function forEachThread(iteratee) {
     99  // We have to be careful here to atomically initiate the operation on every
    100  // thread, with no intervening await. Otherwise, other code could run and
    101  // trigger additional thread operations. Requests on server threads will
    102  // resolve in FIFO order, and this could result in client and server state
    103  // going out of sync.
    104 
    105  const promises = listThreadFronts().map(
    106    // If a thread shuts down while sending the message then it will
    107    // throw. Ignore these exceptions.
    108    t => iteratee(t).catch(e => console.log(e))
    109  );
    110 
    111  return Promise.all(promises);
    112 }
    113 
    114 function resume(thread) {
    115  return lookupThreadFront(thread).resume();
    116 }
    117 
    118 function stepIn(thread, frameId) {
    119  return lookupThreadFront(thread).stepIn(frameId);
    120 }
    121 
    122 function stepOver(thread, frameId) {
    123  return lookupThreadFront(thread).stepOver(frameId);
    124 }
    125 
    126 function stepOut(thread, frameId) {
    127  return lookupThreadFront(thread).stepOut(frameId);
    128 }
    129 
    130 function restart(thread, frameId) {
    131  return lookupThreadFront(thread).restart(frameId);
    132 }
    133 
    134 function breakOnNext(thread) {
    135  return lookupThreadFront(thread).breakOnNext();
    136 }
    137 
    138 async function sourceContents({ actor, thread }) {
    139  const sourceThreadFront = lookupThreadFront(thread);
    140  const sourceFront = sourceThreadFront.source({ actor });
    141  const { source, contentType } = await sourceFront.source();
    142  return { source, contentType };
    143 }
    144 
    145 async function setXHRBreakpoint(path, method) {
    146  const hasWatcherSupport = commands.targetCommand.hasTargetWatcherSupport();
    147  if (!hasWatcherSupport) {
    148    // Without watcher support, forward setXHRBreakpoint to all threads.
    149    await forEachThread(thread => thread.setXHRBreakpoint(path, method));
    150    return;
    151  }
    152  const breakpointsFront =
    153    await commands.targetCommand.watcherFront.getBreakpointListActor();
    154  await breakpointsFront.setXHRBreakpoint(path, method);
    155 }
    156 
    157 async function removeXHRBreakpoint(path, method) {
    158  const hasWatcherSupport = commands.targetCommand.hasTargetWatcherSupport();
    159  if (!hasWatcherSupport) {
    160    // Without watcher support, forward removeXHRBreakpoint to all threads.
    161    await forEachThread(thread => thread.removeXHRBreakpoint(path, method));
    162    return;
    163  }
    164  const breakpointsFront =
    165    await commands.targetCommand.watcherFront.getBreakpointListActor();
    166  await breakpointsFront.removeXHRBreakpoint(path, method);
    167 }
    168 
    169 export function toggleJavaScriptEnabled(enabled) {
    170  return commands.targetConfigurationCommand.updateConfiguration({
    171    javascriptEnabled: enabled,
    172  });
    173 }
    174 
    175 async function addWatchpoint(object, property, label, watchpointType) {
    176  if (!currentTarget().getTrait("watchpoints")) {
    177    return;
    178  }
    179  const objectFront = createObjectFront(object);
    180  await objectFront.addWatchpoint(property, label, watchpointType);
    181 }
    182 
    183 async function removeWatchpoint(object, property) {
    184  if (!currentTarget().getTrait("watchpoints")) {
    185    return;
    186  }
    187  const objectFront = createObjectFront(object);
    188  await objectFront.removeWatchpoint(property);
    189 }
    190 
    191 function hasBreakpoint(location) {
    192  return !!breakpoints[makeBreakpointServerLocationId(location)];
    193 }
    194 
    195 function getServerBreakpointsList() {
    196  return Object.values(breakpoints);
    197 }
    198 
    199 async function setBreakpoint(location, options) {
    200  const breakpoint = breakpoints[makeBreakpointServerLocationId(location)];
    201  if (
    202    breakpoint &&
    203    JSON.stringify(breakpoint.options) == JSON.stringify(options)
    204  ) {
    205    return null;
    206  }
    207  breakpoints[makeBreakpointServerLocationId(location)] = { location, options };
    208 
    209  // Map frontend options to a more restricted subset of what
    210  // the server supports. For example frontend uses `hidden` attribute
    211  // which isn't meant to be passed to the server.
    212  // (note that protocol.js specification isn't enough to filter attributes,
    213  //  all primitive attributes will be passed as-is)
    214  const serverOptions = {
    215    condition: options.condition,
    216    logValue: options.logValue,
    217    showStacktrace: options.showStacktrace,
    218  };
    219  const hasWatcherSupport = commands.targetCommand.hasTargetWatcherSupport();
    220  if (!hasWatcherSupport) {
    221    // Without watcher support, unconditionally forward setBreakpoint to all threads.
    222    return forEachThread(async thread =>
    223      thread.setBreakpoint(location, serverOptions)
    224    );
    225  }
    226  const breakpointsFront =
    227    await commands.targetCommand.watcherFront.getBreakpointListActor();
    228  await breakpointsFront.setBreakpoint(location, serverOptions);
    229 
    230  // Call setBreakpoint for threads linked to targets
    231  // not managed by the watcher.
    232  return forEachThread(async thread => {
    233    if (
    234      !commands.targetCommand.hasTargetWatcherSupport(
    235        thread.targetFront.targetType
    236      )
    237    ) {
    238      return thread.setBreakpoint(location, serverOptions);
    239    }
    240 
    241    return Promise.resolve();
    242  });
    243 }
    244 
    245 async function removeBreakpoint(location) {
    246  delete breakpoints[makeBreakpointServerLocationId(location)];
    247 
    248  const hasWatcherSupport = commands.targetCommand.hasTargetWatcherSupport();
    249  if (!hasWatcherSupport) {
    250    // Without watcher support, unconditionally forward removeBreakpoint to all threads.
    251    return forEachThread(async thread => thread.removeBreakpoint(location));
    252  }
    253  const breakpointsFront =
    254    await commands.targetCommand.watcherFront.getBreakpointListActor();
    255  await breakpointsFront.removeBreakpoint(location);
    256 
    257  // Call removeBreakpoint for threads linked to targets
    258  // not managed by the watcher.
    259  return forEachThread(async thread => {
    260    if (
    261      !commands.targetCommand.hasTargetWatcherSupport(
    262        thread.targetFront.targetType
    263      )
    264    ) {
    265      return thread.removeBreakpoint(location);
    266    }
    267 
    268    return Promise.resolve();
    269  });
    270 }
    271 
    272 async function evaluateExpressions(expressions, options) {
    273  return Promise.all(
    274    expressions.map(expression => evaluate(expression, options))
    275  );
    276 }
    277 
    278 /**
    279 * Evaluate some JS expression in a given thread.
    280 *
    281 * @param {string} expression
    282 * @param {object} options
    283 * @param {string} options.frameId
    284 *                 Optional frame actor ID into which the expression should be evaluated.
    285 * @param {string} options.threadId
    286 *                 Optional thread actor ID into which the expression should be evaluated.
    287 * @param {string} options.selectedNodeActor
    288 *                 Optional node actor ID which related to "$0" in the evaluated expression.
    289 * @param {boolean} options.evalInTracer
    290 *                 To be set to true, if the object actors created during the evaluation
    291 *                 should be registered in the tracer actor Pool.
    292 * @return {object}
    293 *                 See ScriptCommand.execute JS Doc.
    294 */
    295 async function evaluate(
    296  expression,
    297  { frameId, threadId, selectedNodeActor, evalInTracer } = {}
    298 ) {
    299  if (!currentTarget() || !expression) {
    300    return { result: null };
    301  }
    302 
    303  const selectedTargetFront = threadId ? lookupTarget(threadId) : null;
    304 
    305  return commands.scriptCommand.execute(expression, {
    306    frameActor: frameId,
    307    selectedTargetFront,
    308    disableBreaks: true,
    309    selectedNodeActor,
    310    evalInTracer,
    311  });
    312 }
    313 
    314 async function autocomplete(input, cursor, frameId) {
    315  if (!currentTarget() || !input) {
    316    return {};
    317  }
    318  const consoleFront = await currentTarget().getFront("console");
    319  if (!consoleFront) {
    320    return {};
    321  }
    322 
    323  return new Promise(resolve => {
    324    consoleFront.autocomplete(
    325      input,
    326      cursor,
    327      result => resolve(result),
    328      frameId
    329    );
    330  });
    331 }
    332 
    333 async function getFrames(thread) {
    334  const threadFront = lookupThreadFront(thread);
    335  const response = await threadFront.getFrames(0, CALL_STACK_PAGE_SIZE);
    336 
    337  return Promise.all(
    338    response.frames.map((frame, i) => createFrame(thread, frame, i))
    339  );
    340 }
    341 
    342 async function getFrameScopes(frame) {
    343  const frameFront = lookupThreadFront(frame.thread).getActorByID(frame.id);
    344  return frameFront.getEnvironment();
    345 }
    346 
    347 async function pauseOnDebuggerStatement(shouldPauseOnDebuggerStatement) {
    348  await commands.threadConfigurationCommand.updateConfiguration({
    349    shouldPauseOnDebuggerStatement,
    350  });
    351 }
    352 
    353 async function pauseOnExceptions(
    354  shouldPauseOnExceptions,
    355  shouldPauseOnCaughtExceptions
    356 ) {
    357  await commands.threadConfigurationCommand.updateConfiguration({
    358    pauseOnExceptions: shouldPauseOnExceptions,
    359    ignoreCaughtExceptions: !shouldPauseOnCaughtExceptions,
    360  });
    361 }
    362 
    363 async function blackBox(sourceActor, shouldBlackBox, ranges) {
    364  const hasWatcherSupport = commands.targetCommand.hasTargetWatcherSupport();
    365  if (hasWatcherSupport) {
    366    const blackboxingFront =
    367      await commands.targetCommand.watcherFront.getBlackboxingActor();
    368    if (shouldBlackBox) {
    369      await blackboxingFront.blackbox(sourceActor.url, ranges);
    370    } else {
    371      await blackboxingFront.unblackbox(sourceActor.url, ranges);
    372    }
    373  } else {
    374    const sourceFront = currentThreadFront().source({
    375      actor: sourceActor.actor,
    376    });
    377    // If there are no ranges, the whole source is being blackboxed
    378    if (!ranges.length) {
    379      await toggleBlackBoxSourceFront(sourceFront, shouldBlackBox);
    380      return;
    381    }
    382    // Blackbox the specific ranges
    383    for (const range of ranges) {
    384      await toggleBlackBoxSourceFront(sourceFront, shouldBlackBox, range);
    385    }
    386  }
    387 }
    388 
    389 async function toggleBlackBoxSourceFront(sourceFront, shouldBlackBox, range) {
    390  if (shouldBlackBox) {
    391    await sourceFront.blackBox(range);
    392  } else {
    393    await sourceFront.unblackBox(range);
    394  }
    395 }
    396 
    397 async function setSkipPausing(shouldSkip) {
    398  await commands.threadConfigurationCommand.updateConfiguration({
    399    skipBreakpoints: shouldSkip,
    400  });
    401 }
    402 
    403 async function setEventListenerBreakpoints(ids) {
    404  const hasWatcherSupport = commands.targetCommand.hasTargetWatcherSupport();
    405  if (!hasWatcherSupport) {
    406    await forEachThread(thread => thread.setActiveEventBreakpoints(ids));
    407    return;
    408  }
    409  const breakpointListFront =
    410    await commands.targetCommand.watcherFront.getBreakpointListActor();
    411  await breakpointListFront.setActiveEventBreakpoints(ids);
    412 }
    413 
    414 async function getEventListenerBreakpointTypes() {
    415  return currentThreadFront().getAvailableEventBreakpoints();
    416 }
    417 
    418 async function toggleEventLogging(logEventBreakpoints) {
    419  await commands.threadConfigurationCommand.updateConfiguration({
    420    logEventBreakpoints,
    421  });
    422 }
    423 
    424 function getMainThread() {
    425  return currentThreadFront().actor;
    426 }
    427 
    428 async function getSourceActorBreakpointPositions({ thread, actor }, range) {
    429  const sourceThreadFront = lookupThreadFront(thread);
    430  const sourceFront = sourceThreadFront.source({ actor });
    431  return sourceFront.getBreakpointPositionsCompressed(range);
    432 }
    433 
    434 async function getSourceActorBreakableLines({ thread, actor }) {
    435  let actorLines = [];
    436  try {
    437    const sourceThreadFront = lookupThreadFront(thread);
    438    const sourceFront = sourceThreadFront.source({ actor });
    439    actorLines = await sourceFront.getBreakableLines();
    440  } catch (e) {
    441    // Exceptions could be due to the target thread being shut down.
    442    console.warn(`getSourceActorBreakableLines failed: ${e}`);
    443  }
    444 
    445  return actorLines;
    446 }
    447 
    448 function getFrontByID(actorID) {
    449  return commands.client.getFrontByID(actorID);
    450 }
    451 
    452 function fetchAncestorFramePositions(index) {
    453  currentThreadFront().fetchAncestorFramePositions(index);
    454 }
    455 
    456 const clientCommands = {
    457  autocomplete,
    458  blackBox,
    459  createObjectFront,
    460  loadObjectProperties,
    461  releaseActor,
    462  resume,
    463  stepIn,
    464  stepOut,
    465  stepOver,
    466  restart,
    467  breakOnNext,
    468  sourceContents,
    469  getSourceActorBreakpointPositions,
    470  getSourceActorBreakableLines,
    471  hasBreakpoint,
    472  getServerBreakpointsList,
    473  setBreakpoint,
    474  setXHRBreakpoint,
    475  removeXHRBreakpoint,
    476  addWatchpoint,
    477  removeWatchpoint,
    478  removeBreakpoint,
    479  evaluate,
    480  evaluateExpressions,
    481  getFrameScopes,
    482  getFrames,
    483  pauseOnDebuggerStatement,
    484  pauseOnExceptions,
    485  toggleEventLogging,
    486  getMainThread,
    487  setSkipPausing,
    488  setEventListenerBreakpoints,
    489  getEventListenerBreakpointTypes,
    490  getFrontByID,
    491  fetchAncestorFramePositions,
    492  toggleJavaScriptEnabled,
    493 };
    494 
    495 export { setupCommands, clientCommands };