diff options
Diffstat (limited to 'devtools/client/webconsole/test/browser/stub-generator-helpers.js')
-rw-r--r-- | devtools/client/webconsole/test/browser/stub-generator-helpers.js | 437 |
1 files changed, 437 insertions, 0 deletions
diff --git a/devtools/client/webconsole/test/browser/stub-generator-helpers.js b/devtools/client/webconsole/test/browser/stub-generator-helpers.js new file mode 100644 index 0000000000..1159053f49 --- /dev/null +++ b/devtools/client/webconsole/test/browser/stub-generator-helpers.js @@ -0,0 +1,437 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { + getAdHocFrontOrPrimitiveGrip, +} = require("devtools/client/fronts/object"); + +const CHROME_PREFIX = "chrome://mochitests/content/browser/"; +const STUBS_FOLDER = "devtools/client/webconsole/test/node/fixtures/stubs/"; +const STUBS_UPDATE_ENV = "WEBCONSOLE_STUBS_UPDATE"; + +async function createCommandsForTab(tab) { + const { + CommandsFactory, + } = require("devtools/shared/commands/commands-factory"); + const commands = await CommandsFactory.forTab(tab); + return commands; +} + +async function createCommandsForMainProcess() { + const { + CommandsFactory, + } = require("devtools/shared/commands/commands-factory"); + const commands = await CommandsFactory.forMainProcess(); + return commands; +} + +// eslint-disable-next-line complexity +function getCleanedPacket(key, packet) { + const { stubPackets } = require(CHROME_PREFIX + STUBS_FOLDER + "index"); + + // Strip escaped characters. + const safeKey = key + .replace(/\\n/g, "\n") + .replace(/\\r/g, "\r") + .replace(/\\\"/g, `\"`) + .replace(/\\\'/g, `\'`); + + cleanTimeStamp(packet); + // Remove the targetFront property that has a cyclical reference and that we don't need + // in our node tests. + delete packet.targetFront; + + if (!stubPackets.has(safeKey)) { + return packet; + } + + // If the stub already exist, we want to ignore irrelevant properties (generated id, timer, …) + // that might changed and "pollute" the diff resulting from this stub generation. + const existingPacket = stubPackets.get(safeKey); + const res = Object.assign({}, packet, { + from: existingPacket.from, + }); + + if (res.innerWindowID) { + res.innerWindowID = existingPacket.innerWindowID; + } + + if (res.startedDateTime) { + res.startedDateTime = existingPacket.startedDateTime; + } + + if (res.channelId) { + res.channelId = existingPacket.channelId; + } + + if (res.resultID) { + res.resultID = existingPacket.resultID; + } + + if (res.message) { + if (res.message.timer) { + // Clean timer properties on the message. + // Those properties are found on console.time, timeLog and timeEnd calls, + // and those time can vary, which is why we need to clean them. + if ("duration" in res.message.timer) { + res.message.timer.duration = existingPacket.message.timer.duration; + } + } + // Clean innerWindowId on the message prop. + if (existingPacket.message.innerWindowID) { + res.message.innerWindowID = existingPacket.message.innerWindowID; + } + + if (Array.isArray(res.message.arguments)) { + res.message.arguments = res.message.arguments.map((argument, i) => { + if (!argument || typeof argument !== "object") { + return argument; + } + + const newArgument = Object.assign({}, argument); + const existingArgument = existingPacket.message.arguments[i]; + + if (existingArgument && newArgument._grip) { + // `window`'s properties count can vary from OS to OS, so we + // clean the `ownPropertyLength` property from the grip. + if (newArgument._grip.class === "Window") { + newArgument._grip.ownPropertyLength = + existingArgument._grip.ownPropertyLength; + } + } + return newArgument; + }); + } + + if (res.message.sourceId) { + res.message.sourceId = existingPacket.message.sourceId; + } + + if (Array.isArray(res.message.stacktrace)) { + res.message.stacktrace = res.message.stacktrace.map((frame, i) => { + const existingFrame = existingPacket.message.stacktrace[i]; + if (frame && existingFrame && frame.sourceId) { + frame.sourceId = existingFrame.sourceId; + } + return frame; + }); + } + } + + if (res.eventActor) { + // Clean startedDateTime on network messages. + res.eventActor.startedDateTime = existingPacket.startedDateTime; + } + + if (res.pageError) { + // Clean innerWindowID on pageError messages. + res.pageError.innerWindowID = existingPacket.pageError.innerWindowID; + + if (res.pageError.sourceId) { + res.pageError.sourceId = existingPacket.pageError.sourceId; + } + + if ( + Array.isArray(res.pageError.stacktrace) && + Array.isArray(existingPacket.pageError.stacktrace) + ) { + res.pageError.stacktrace = res.pageError.stacktrace.map((frame, i) => { + const existingFrame = existingPacket.pageError.stacktrace[i]; + if (frame && existingFrame && frame.sourceId) { + frame.sourceId = existingFrame.sourceId; + } + return frame; + }); + } + } + + if (Array.isArray(res.exceptionStack)) { + res.exceptionStack = res.exceptionStack.map((frame, i) => { + const existingFrame = existingPacket.exceptionStack[i]; + // We're replacing sourceId here even if the property in frame is null to avoid + // a frequent intermittent. The sourceId is retrieved from the Debugger#findSources + // API, which is not deterministic (See https://searchfox.org/mozilla-central/rev/b172dd415c475e8b2899560e6005b3a953bead2a/js/src/doc/Debugger/Debugger.md#367-375) + // This should be fixed in Bug 1717037. + if (frame && existingFrame && "sourceId" in frame) { + frame.sourceId = existingFrame.sourceId; + } + return frame; + }); + } + + if (res.frame && existingPacket.frame) { + res.frame.sourceId = existingPacket.frame.sourceId; + } + + if (res.packet) { + const override = {}; + const keys = ["totalTime", "from", "contentSize", "transferredSize"]; + keys.forEach(x => { + if (res.packet[x] !== undefined) { + override[x] = existingPacket.packet[key]; + } + }); + res.packet = Object.assign({}, res.packet, override); + } + + if (res.startedDateTime) { + res.startedDateTime = existingPacket.startedDateTime; + } + + if (res.totalTime && existingPacket.totalTime) { + res.totalTime = existingPacket.totalTime; + } + + if (res.securityState && existingPacket.securityState) { + res.securityState = existingPacket.securityState; + } + + // waitingTime can be very small and rounded to 0. However this is still a + // valid waiting time, so check isNaN instead of a simple truthy check. + if (!isNaN(res.waitingTime) && existingPacket.waitingTime) { + res.waitingTime = existingPacket.waitingTime; + } + + return res; +} + +function cleanTimeStamp(packet) { + // We want to have the same timestamp for every stub, so they won't be re-sorted when + // adding them to the store. + const uniqueTimeStamp = 1572867483805; + // lowercased timestamp + if (packet.timestamp) { + packet.timestamp = uniqueTimeStamp; + } + + // camelcased timestamp + if (packet.timeStamp) { + packet.timeStamp = uniqueTimeStamp; + } + + if (packet.startTime) { + packet.startTime = uniqueTimeStamp; + } + + if (packet?.message?.timeStamp) { + packet.message.timeStamp = uniqueTimeStamp; + } + + if (packet?.result?._grip?.preview?.timestamp) { + packet.result._grip.preview.timestamp = uniqueTimeStamp; + } + + if (packet?.result?._grip?.promiseState?.creationTimestamp) { + packet.result._grip.promiseState.creationTimestamp = uniqueTimeStamp; + } + + if (packet?.exception?._grip?.preview?.timestamp) { + packet.exception._grip.preview.timestamp = uniqueTimeStamp; + } + + if (packet?.eventActor?.timeStamp) { + packet.eventActor.timeStamp = uniqueTimeStamp; + } + + if (packet?.pageError?.timeStamp) { + packet.pageError.timeStamp = uniqueTimeStamp; + } +} + +/** + * Write stubs to a given file + * + * @param {String} fileName: The file to write the stubs in. + * @param {Map} packets: A Map of the packets. + * @param {Boolean} isNetworkMessage: Is the packets are networkMessage packets + */ +async function writeStubsToFile(fileName, packets, isNetworkMessage) { + const mozRepo = Services.env.get("MOZ_DEVELOPER_REPO_DIR"); + const filePath = `${mozRepo}/${STUBS_FOLDER + fileName}`; + + const serializedPackets = Array.from(packets.entries()).map( + ([key, packet]) => { + const stringifiedPacket = getSerializedPacket(packet); + return `rawPackets.set(\`${key}\`, ${stringifiedPacket});`; + } + ); + + const fileContent = `/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* eslint-disable max-len */ + +"use strict"; + +/* + * THIS FILE IS AUTOGENERATED. DO NOT MODIFY BY HAND. SEE devtools/client/webconsole/test/README.md. + */ + +const { + parsePacketsWithFronts, +} = require("chrome://mochitests/content/browser/devtools/client/webconsole/test/browser/stub-generator-helpers.js"); +const { prepareMessage } = require("resource://devtools/client/webconsole/utils/messages.js"); +const { + ConsoleMessage, + NetworkEventMessage, +} = require("resource://devtools/client/webconsole/types.js"); + +const rawPackets = new Map(); +${serializedPackets.join("\n\n")} + + +const stubPackets = parsePacketsWithFronts(rawPackets); + +const stubPreparedMessages = new Map(); +for (const [key, packet] of Array.from(stubPackets.entries())) { + const transformedPacket = prepareMessage(${"packet"}, { + getNextId: () => "1", + }); + const message = ${ + isNetworkMessage + ? "NetworkEventMessage(transformedPacket);" + : "ConsoleMessage(transformedPacket);" + } + stubPreparedMessages.set(key, message); +} + +module.exports = { + rawPackets, + stubPreparedMessages, + stubPackets, +}; +`; + + await IOUtils.write(filePath, new TextEncoder().encode(fileContent)); +} + +function getStubFile(fileName) { + return require(CHROME_PREFIX + STUBS_FOLDER + fileName); +} + +function sortObjectKeys(obj) { + const isArray = Array.isArray(obj); + const isObject = Object.prototype.toString.call(obj) === "[object Object]"; + const isFront = obj?._grip; + + if (isObject && !isFront) { + // Reorder keys for objects, but skip fronts to avoid infinite recursion. + const sortedKeys = Object.keys(obj).sort((k1, k2) => k1.localeCompare(k2)); + const withSortedKeys = {}; + sortedKeys.forEach(k => { + withSortedKeys[k] = k !== "stacktrace" ? sortObjectKeys(obj[k]) : obj[k]; + }); + return withSortedKeys; + } else if (isArray) { + return obj.map(item => sortObjectKeys(item)); + } + return obj; +} + +/** + * @param {Object} packet + * The packet to serialize. + * @param {Object} options + * @param {Boolean} options.sortKeys + * Pass true to sort all keys alphabetically in the packet before serialization. + * For instance stub comparison should not fail if the order of properties changed. + * @param {Boolean} options.replaceActorIds + * Pass true to replace actorIDs with a fake one so it's easier to compare stubs + * that includes grips. + */ +function getSerializedPacket( + packet, + { sortKeys = false, replaceActorIds = false } = {} +) { + if (sortKeys) { + packet = sortObjectKeys(packet); + } + + const actorIdPlaceholder = "XXX"; + + return JSON.stringify( + packet, + function (key, value) { + // The message can have fronts that we need to serialize + if (value && value._grip) { + return { + _grip: value._grip, + actorID: replaceActorIds ? actorIdPlaceholder : value.actorID, + }; + } + + if ( + replaceActorIds && + (key === "actor" || key === "actorID" || key === "sourceId") && + typeof value === "string" + ) { + return actorIdPlaceholder; + } + + if (key === "resourceId") { + return undefined; + } + + return value; + }, + 2 + ); +} + +/** + * + * @param {Map} rawPackets + */ +function parsePacketsWithFronts(rawPackets) { + const packets = new Map(); + for (const [key, packet] of rawPackets.entries()) { + const newPacket = parsePacketAndCreateFronts(packet); + packets.set(key, newPacket); + } + return packets; +} + +function parsePacketAndCreateFronts(packet) { + if (!packet) { + return packet; + } + if (Array.isArray(packet)) { + packet.forEach(parsePacketAndCreateFronts); + } + if (typeof packet === "object") { + for (const [key, value] of Object.entries(packet)) { + if (value?._grip) { + // The message of an error grip might be a longString. + if (value._grip?.preview?.message?._grip) { + value._grip.preview.message = value._grip.preview.message._grip; + } + + packet[key] = getAdHocFrontOrPrimitiveGrip(value._grip, { + conn: { + poolFor: () => {}, + addActorPool: () => {}, + getFrontByID: () => {}, + }, + manage: () => {}, + }); + } else { + packet[key] = parsePacketAndCreateFronts(value); + } + } + } + + return packet; +} + +module.exports = { + STUBS_UPDATE_ENV, + createCommandsForTab, + createCommandsForMainProcess, + getStubFile, + getCleanedPacket, + getSerializedPacket, + parsePacketsWithFronts, + parsePacketAndCreateFronts, + writeStubsToFile, +}; |