tor-browser

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

test-line-tracer.js (5846B)


      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 "use strict";
      6 
      7 const { JSTracer } = ChromeUtils.importESModule(
      8  "resource://devtools/server/tracer/tracer.sys.mjs",
      9  { global: "shared" }
     10 );
     11 
     12 let lineToTrace;
     13 
     14 const fileContents = new Map();
     15 
     16 function getFileContent(url) {
     17  let content = fileContents.get(url);
     18  if (content) {
     19    return content;
     20  }
     21  content = readURI(url).split("\n");
     22  fileContents.set(url, content);
     23  return content;
     24 }
     25 
     26 function isNestedFrame(frame, topFrame) {
     27  if (frame.older) {
     28    // older will be a Debugger.Frame
     29    while ((frame = frame.older)) {
     30      if (frame == topFrame) {
     31        return true;
     32      }
     33    }
     34  } else if (frame.olderSavedFrame) {
     35    // olderSavedFrame will be a SavedStack object
     36    frame = frame.olderSavedFrame;
     37    const { lineNumber, columnNumber } = topFrame.script.getOffsetMetadata(
     38      top.offset
     39    );
     40    while ((frame = frame.parent || frame.asyncParent)) {
     41      if (
     42        frame.source == topFrame.script.source.url &&
     43        frame.line == lineNumber &&
     44        frame.column == columnNumber
     45      ) {
     46        return true;
     47      }
     48    }
     49  }
     50  return false;
     51 }
     52 
     53 // Store the top most frame running at `lineToTrace` line.
     54 // We will then log all frames which are children of this top one.
     55 let initialFrame = null;
     56 let previousSourceUrl = null;
     57 
     58 function traceFrame({ frame }) {
     59  const { script } = frame;
     60  const { lineNumber, columnNumber } = script.getOffsetMetadata(frame.offset);
     61  if (lineToTrace) {
     62    if (lineNumber == lineToTrace) {
     63      // Stop the first tracer started from `exports.start()` which was only waiting for the particular test script line to run
     64      JSTracer.stopTracing();
     65 
     66      const { url } = script.source;
     67      const filename = url.substr(url.lastIndexOf("/") + 1);
     68      const line = getFileContent(url)[lineNumber - 1];
     69      logStep(`Start tracing ${filename} @ ${lineNumber} :: ${line}`);
     70      previousSourceUrl = url;
     71      // Restart a new tracer which would go track all the globals and not restrict to the test script.
     72      const tracerOptions = {
     73        // Ensure tracing all globals in this thread
     74        traceAllGlobals: true,
     75        // Ensure tracing each execution within functions (and not only function calls)
     76        traceSteps: true,
     77      };
     78      lineToTrace = null;
     79      JSTracer.startTracing(tracerOptions);
     80    }
     81    return false;
     82  }
     83  // We executed the test line we wanted to trace and now log all frames via a second tracer instance
     84 
     85  // First pick up the very first executed frame, so that we can trace all nested frame from this one.
     86  if (!initialFrame) {
     87    initialFrame = frame;
     88  } else if (initialFrame.terminated) {
     89    // If the traced top frame completed its execution, stop tracing.
     90    // Note that terminated will only be true once any possibly asynchronous work of the traced function
     91    // is done executing.
     92    logStep("End of execution");
     93    exports.stop();
     94    return false;
     95  } else if (!initialFrame.onStack) {
     96    // If we are done executing the traced Frame, it will be declared out of the stack.
     97    // By we should keep tracing as, if the traced Frame involves async work, it may be later restored onto the stack.
     98    return false;
     99  } else if (frame != initialFrame && !isNestedFrame(frame, initialFrame)) {
    100    // Then, only log frame which ultimately related to this first frame we picked.
    101    // Because of asynchronous calls and concurrent event loops, we may have in-between frames
    102    // that we ignore which relates to another event loop and another top frame.
    103    //
    104    // Note that the tracer may notify us about the exact same Frame object multiple times.
    105    // (its offset/location will change, but the object will be the same)
    106    return false;
    107  }
    108 
    109  const { url } = script.source;
    110 
    111  // Print the full source URL each time we start tracing a new source
    112  if (previousSourceUrl && previousSourceUrl !== url) {
    113    logStep("");
    114    logStep(url);
    115    // Log a grey line separator
    116    logStep(`\x1b[2m` + `\u2500`.repeat(url.length) + `\x1b[0m`);
    117    previousSourceUrl = url;
    118  }
    119 
    120  const line = getFileContent(url)[lineNumber - 1];
    121  // Grey out the beginning of the line, before frame's column,
    122  // and display an arrow before displaying the rest of the line.
    123  const code =
    124    "\x1b[2m" +
    125    line.substr(0, columnNumber - 1) +
    126    "\x1b[0m" +
    127    "\u21A6 " +
    128    line.substr(columnNumber - 1);
    129 
    130  const position = (lineNumber + ":" + columnNumber).padEnd(7);
    131  logStep(`${position} \u007C ${code}`);
    132 
    133  // Disable builtin tracer logging
    134  return false;
    135 }
    136 
    137 function logStep(message) {
    138  dump(` \x1b[2m[STEP]\x1b[0m ${message}\n`);
    139 }
    140 
    141 const tracingListener = {
    142  onTracingFrame: traceFrame,
    143  onTracingFrameStep: traceFrame,
    144 };
    145 
    146 exports.start = function (testGlobal, testUrl, line) {
    147  lineToTrace = line;
    148  const tracerOptions = {
    149    global: testGlobal,
    150    // Ensure tracing each execution within functions (and not only function calls)
    151    traceSteps: true,
    152    // Only trace the running test and nothing else
    153    filterFrameSourceUrl: testUrl,
    154  };
    155  JSTracer.startTracing(tracerOptions);
    156  JSTracer.addTracingListener(tracingListener);
    157 };
    158 
    159 exports.stop = function () {
    160  JSTracer.stopTracing();
    161  JSTracer.removeTracingListener(tracingListener);
    162 };
    163 
    164 function readURI(uri) {
    165  const { NetUtil } = ChromeUtils.importESModule(
    166    "resource://gre/modules/NetUtil.sys.mjs",
    167    { global: "contextual" }
    168  );
    169  const stream = NetUtil.newChannel({
    170    uri: NetUtil.newURI(uri, "UTF-8"),
    171    loadUsingSystemPrincipal: true,
    172  }).open();
    173  const count = stream.available();
    174  const data = NetUtil.readInputStreamToString(stream, count, {
    175    charset: "UTF-8",
    176  });
    177 
    178  stream.close();
    179  return data;
    180 }