tor-browser

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

stub-generator-helpers.js (12563B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 const {
      7  getAdHocFrontOrPrimitiveGrip,
      8 } = require("devtools/client/fronts/object");
      9 
     10 const CHROME_PREFIX = "chrome://mochitests/content/browser/";
     11 const STUBS_FOLDER = "devtools/client/webconsole/test/node/fixtures/stubs/";
     12 const STUBS_UPDATE_ENV = "WEBCONSOLE_STUBS_UPDATE";
     13 
     14 async function createCommandsForTab(tab) {
     15  const {
     16    CommandsFactory,
     17  } = require("devtools/shared/commands/commands-factory");
     18  const commands = await CommandsFactory.forTab(tab);
     19  return commands;
     20 }
     21 
     22 async function createCommandsForMainProcess() {
     23  const {
     24    CommandsFactory,
     25  } = require("devtools/shared/commands/commands-factory");
     26  const commands = await CommandsFactory.forMainProcess();
     27  return commands;
     28 }
     29 
     30 // eslint-disable-next-line complexity
     31 function getCleanedPacket(key, packet) {
     32  const { stubPackets } = require(CHROME_PREFIX + STUBS_FOLDER + "index");
     33 
     34  // Strip escaped characters.
     35  const safeKey = key
     36    .replace(/\\n/g, "\n")
     37    .replace(/\\r/g, "\r")
     38    .replace(/\\\"/g, `\"`)
     39    .replace(/\\\'/g, `\'`);
     40 
     41  cleanTimeStamp(packet);
     42  // Remove the targetFront property that has a cyclical reference and that we don't need
     43  // in our node tests.
     44  delete packet.targetFront;
     45 
     46  if (!stubPackets.has(safeKey)) {
     47    return packet;
     48  }
     49 
     50  // If the stub already exist, we want to ignore irrelevant properties (generated id, timer, …)
     51  // that might changed and "pollute" the diff resulting from this stub generation.
     52  const existingPacket = stubPackets.get(safeKey);
     53  const res = Object.assign({}, packet, {
     54    from: existingPacket.from,
     55  });
     56 
     57  if (res.innerWindowID) {
     58    res.innerWindowID = existingPacket.innerWindowID;
     59  }
     60 
     61  if (res.startedDateTime) {
     62    res.startedDateTime = existingPacket.startedDateTime;
     63  }
     64 
     65  if (res.channelId) {
     66    res.channelId = existingPacket.channelId;
     67  }
     68 
     69  if (res.resultID) {
     70    res.resultID = existingPacket.resultID;
     71  }
     72 
     73  if (res.timer) {
     74    // Clean timer properties on the message.
     75    // Those properties are found on console.time, timeLog and timeEnd calls,
     76    // and those time can vary, which is why we need to clean them.
     77    if ("duration" in res.timer) {
     78      res.timer.duration = existingPacket.timer.duration;
     79    }
     80  }
     81 
     82  // Clean innerWindowId on the message prop.
     83  if (existingPacket.innerWindowID) {
     84    res.innerWindowID = existingPacket.innerWindowID;
     85  }
     86 
     87  if (Array.isArray(res.arguments)) {
     88    res.arguments = res.arguments.map((argument, i) => {
     89      if (!argument || typeof argument !== "object") {
     90        return argument;
     91      }
     92 
     93      const newArgument = Object.assign({}, argument);
     94      const existingArgument = existingPacket.arguments[i];
     95 
     96      if (existingArgument && newArgument._grip) {
     97        // `window`'s properties count can vary from OS to OS, so we
     98        // clean the `ownPropertyLength` property from the grip.
     99        if (newArgument._grip.class === "Window") {
    100          newArgument._grip.ownPropertyLength =
    101            existingArgument._grip.ownPropertyLength;
    102        }
    103      }
    104      return newArgument;
    105    });
    106  }
    107 
    108  if (res.sourceId) {
    109    res.sourceId = existingPacket.sourceId;
    110  }
    111 
    112  if (Array.isArray(res.stacktrace)) {
    113    res.stacktrace = res.stacktrace.map((frame, i) => {
    114      const existingFrame = existingPacket.stacktrace[i];
    115      if (frame && existingFrame && frame.sourceId) {
    116        frame.sourceId = existingFrame.sourceId;
    117      }
    118      return frame;
    119    });
    120  }
    121 
    122  if (res.eventActor) {
    123    // Clean startedDateTime on network messages.
    124    res.eventActor.startedDateTime = existingPacket.startedDateTime;
    125  }
    126 
    127  if (res.pageError) {
    128    // Clean innerWindowID on pageError messages.
    129    res.pageError.innerWindowID = existingPacket.pageError.innerWindowID;
    130 
    131    if (res.pageError.sourceId) {
    132      res.pageError.sourceId = existingPacket.pageError.sourceId;
    133    }
    134 
    135    if (
    136      Array.isArray(res.pageError.stacktrace) &&
    137      Array.isArray(existingPacket.pageError.stacktrace)
    138    ) {
    139      res.pageError.stacktrace = res.pageError.stacktrace.map((frame, i) => {
    140        const existingFrame = existingPacket.pageError.stacktrace[i];
    141        if (frame && existingFrame && frame.sourceId) {
    142          frame.sourceId = existingFrame.sourceId;
    143        }
    144        return frame;
    145      });
    146    }
    147  }
    148 
    149  if (Array.isArray(res.exceptionStack)) {
    150    res.exceptionStack = res.exceptionStack.map((frame, i) => {
    151      const existingFrame = existingPacket.exceptionStack[i];
    152      // We're replacing sourceId here even if the property in frame is null to avoid
    153      // a frequent intermittent. The sourceId is retrieved from the Debugger#findSources
    154      // API, which is not deterministic (See https://searchfox.org/mozilla-central/rev/b172dd415c475e8b2899560e6005b3a953bead2a/js/src/doc/Debugger/Debugger.md#367-375)
    155      // This should be fixed in Bug 1717037.
    156      if (frame && existingFrame && "sourceId" in frame) {
    157        frame.sourceId = existingFrame.sourceId;
    158      }
    159      return frame;
    160    });
    161  }
    162 
    163  if (res.frame && existingPacket.frame) {
    164    res.frame.sourceId = existingPacket.frame.sourceId;
    165  }
    166 
    167  if (res.packet) {
    168    const override = {};
    169    const keys = ["totalTime", "from", "contentSize", "transferredSize"];
    170    keys.forEach(x => {
    171      if (res.packet[x] !== undefined) {
    172        override[x] = existingPacket.packet[key];
    173      }
    174    });
    175    res.packet = Object.assign({}, res.packet, override);
    176  }
    177 
    178  if (res.startedDateTime) {
    179    res.startedDateTime = existingPacket.startedDateTime;
    180  }
    181 
    182  if (res.totalTime && existingPacket.totalTime) {
    183    res.totalTime = existingPacket.totalTime;
    184  }
    185 
    186  if (res.securityState && existingPacket.securityState) {
    187    res.securityState = existingPacket.securityState;
    188  }
    189 
    190  // waitingTime can be very small and rounded to 0. However this is still a
    191  // valid waiting time, so check isNaN instead of a simple truthy check.
    192  if (!isNaN(res.waitingTime) && existingPacket.waitingTime) {
    193    res.waitingTime = existingPacket.waitingTime;
    194  }
    195 
    196  return res;
    197 }
    198 
    199 // eslint-disable-next-line complexity
    200 function cleanTimeStamp(packet) {
    201  // We want to have the same timestamp for every stub, so they won't be re-sorted when
    202  // adding them to the store.
    203  const uniqueTimeStamp = 1572867483805;
    204  // lowercased timestamp
    205  if (packet.timestamp) {
    206    packet.timestamp = uniqueTimeStamp;
    207  }
    208 
    209  // camelcased timestamp
    210  if (packet.timeStamp) {
    211    packet.timeStamp = uniqueTimeStamp;
    212  }
    213 
    214  if (packet.startTime) {
    215    packet.startTime = uniqueTimeStamp;
    216  }
    217 
    218  if (packet?.timeStamp) {
    219    packet.timeStamp = uniqueTimeStamp;
    220  }
    221 
    222  if (packet?.result?._grip?.preview?.timestamp) {
    223    packet.result._grip.preview.timestamp = uniqueTimeStamp;
    224  }
    225 
    226  if (packet?.result?._grip?.promiseState?.creationTimestamp) {
    227    packet.result._grip.promiseState.creationTimestamp = uniqueTimeStamp;
    228  }
    229 
    230  if (packet?.exception?._grip?.preview?.timestamp) {
    231    packet.exception._grip.preview.timestamp = uniqueTimeStamp;
    232  }
    233 
    234  if (packet?.eventActor?.timeStamp) {
    235    packet.eventActor.timeStamp = uniqueTimeStamp;
    236  }
    237 
    238  if (packet?.pageError?.timeStamp) {
    239    packet.pageError.timeStamp = uniqueTimeStamp;
    240  }
    241 }
    242 
    243 /**
    244 * Write stubs to a given file
    245 *
    246 * @param {string} fileName: The file to write the stubs in.
    247 * @param {Map} packets: A Map of the packets.
    248 * @param {boolean} isNetworkMessage: Is the packets are networkMessage packets
    249 */
    250 async function writeStubsToFile(fileName, packets, isNetworkMessage) {
    251  const mozRepo = Services.env.get("MOZ_DEVELOPER_REPO_DIR");
    252  const filePath = `${mozRepo}/${STUBS_FOLDER + fileName}`;
    253 
    254  const serializedPackets = Array.from(packets.entries()).map(
    255    ([key, packet]) => {
    256      const stringifiedPacket = getSerializedPacket(packet);
    257      return `rawPackets.set(\`${key}\`, ${stringifiedPacket});`;
    258    }
    259  );
    260 
    261  const fileContent = `/* Any copyright is dedicated to the Public Domain.
    262  http://creativecommons.org/publicdomain/zero/1.0/ */
    263 /* eslint-disable max-len */
    264 
    265 "use strict";
    266 
    267 /*
    268 * THIS FILE IS AUTOGENERATED. DO NOT MODIFY BY HAND. SEE devtools/client/webconsole/test/README.md.
    269 */
    270 
    271 const {
    272  parsePacketsWithFronts,
    273 } = require("chrome://mochitests/content/browser/devtools/client/webconsole/test/browser/stub-generator-helpers.js");
    274 const { prepareMessage } = require("resource://devtools/client/webconsole/utils/messages.js");
    275 const {
    276  ConsoleMessage,
    277  NetworkEventMessage,
    278 } = require("resource://devtools/client/webconsole/types.js");
    279 
    280 const rawPackets = new Map();
    281 ${serializedPackets.join("\n\n")}
    282 
    283 
    284 const stubPackets = parsePacketsWithFronts(rawPackets);
    285 
    286 const stubPreparedMessages = new Map();
    287 for (const [key, packet] of Array.from(stubPackets.entries())) {
    288  const transformedPacket = prepareMessage(${"packet"}, {
    289    getNextId: () => "1",
    290  });
    291  const message = ${
    292    isNetworkMessage
    293      ? "NetworkEventMessage(transformedPacket);"
    294      : "ConsoleMessage(transformedPacket);"
    295  }
    296  stubPreparedMessages.set(key, message);
    297 }
    298 
    299 module.exports = {
    300  rawPackets,
    301  stubPreparedMessages,
    302  stubPackets,
    303 };
    304 `;
    305 
    306  await IOUtils.write(filePath, new TextEncoder().encode(fileContent));
    307 }
    308 
    309 function getStubFile(fileName) {
    310  return require(CHROME_PREFIX + STUBS_FOLDER + fileName);
    311 }
    312 
    313 function sortObjectKeys(obj) {
    314  const isArray = Array.isArray(obj);
    315  const isObject = Object.prototype.toString.call(obj) === "[object Object]";
    316  const isFront = obj?._grip;
    317 
    318  if (isObject && !isFront) {
    319    // Reorder keys for objects, but skip fronts to avoid infinite recursion.
    320    const sortedKeys = Object.keys(obj).sort((k1, k2) => k1.localeCompare(k2));
    321    const withSortedKeys = {};
    322    sortedKeys.forEach(k => {
    323      withSortedKeys[k] = k !== "stacktrace" ? sortObjectKeys(obj[k]) : obj[k];
    324    });
    325    return withSortedKeys;
    326  } else if (isArray) {
    327    return obj.map(item => sortObjectKeys(item));
    328  }
    329  return obj;
    330 }
    331 
    332 /**
    333 * @param {object} packet
    334 *        The packet to serialize.
    335 * @param {object} options
    336 * @param {boolean} options.sortKeys
    337 *        Pass true to sort all keys alphabetically in the packet before serialization.
    338 *        For instance stub comparison should not fail if the order of properties changed.
    339 * @param {boolean} options.replaceActorIds
    340 *        Pass true to replace actorIDs with a fake one so it's easier to compare stubs
    341 *        that includes grips.
    342 */
    343 function getSerializedPacket(
    344  packet,
    345  { sortKeys = false, replaceActorIds = false } = {}
    346 ) {
    347  if (sortKeys) {
    348    packet = sortObjectKeys(packet);
    349  }
    350 
    351  const actorIdPlaceholder = "XXX";
    352 
    353  return JSON.stringify(
    354    packet,
    355    function (key, value) {
    356      // The message can have fronts that we need to serialize
    357      if (value && value._grip) {
    358        return {
    359          _grip: value._grip,
    360          actorID: replaceActorIds ? actorIdPlaceholder : value.actorID,
    361        };
    362      }
    363 
    364      if (
    365        replaceActorIds &&
    366        (key === "actor" || key === "actorID" || key === "sourceId") &&
    367        typeof value === "string"
    368      ) {
    369        return actorIdPlaceholder;
    370      }
    371 
    372      if (key === "resourceId") {
    373        return undefined;
    374      }
    375 
    376      return value;
    377    },
    378    2
    379  );
    380 }
    381 
    382 /**
    383 *
    384 * @param {Map} rawPackets
    385 */
    386 function parsePacketsWithFronts(rawPackets) {
    387  const packets = new Map();
    388  for (const [key, packet] of rawPackets.entries()) {
    389    const newPacket = parsePacketAndCreateFronts(packet);
    390    packets.set(key, newPacket);
    391  }
    392  return packets;
    393 }
    394 
    395 function parsePacketAndCreateFronts(packet) {
    396  if (!packet) {
    397    return packet;
    398  }
    399  if (Array.isArray(packet)) {
    400    packet.forEach(parsePacketAndCreateFronts);
    401  }
    402  if (typeof packet === "object") {
    403    for (const [key, value] of Object.entries(packet)) {
    404      if (value?._grip) {
    405        // The message of an error grip might be a longString.
    406        if (value._grip?.preview?.message?._grip) {
    407          value._grip.preview.message = value._grip.preview.message._grip;
    408        }
    409 
    410        packet[key] = getAdHocFrontOrPrimitiveGrip(value._grip, {
    411          conn: {
    412            poolFor: () => {},
    413            addActorPool: () => {},
    414            getFrontByID: () => {},
    415          },
    416          manage: () => {},
    417        });
    418      } else {
    419        packet[key] = parsePacketAndCreateFronts(value);
    420      }
    421    }
    422  }
    423 
    424  return packet;
    425 }
    426 
    427 module.exports = {
    428  STUBS_UPDATE_ENV,
    429  createCommandsForTab,
    430  createCommandsForMainProcess,
    431  getStubFile,
    432  getCleanedPacket,
    433  getSerializedPacket,
    434  parsePacketsWithFronts,
    435  parsePacketAndCreateFronts,
    436  writeStubsToFile,
    437 };