forked from mirrors/gecko-dev
545 lines
15 KiB
JavaScript
545 lines
15 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
"use strict";
|
|
|
|
const ChromeUtils = require("ChromeUtils");
|
|
const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
|
|
|
|
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 createResourceWatcherForTab(tab) {
|
|
const { TargetFactory } = require("devtools/client/framework/target");
|
|
const target = await TargetFactory.forTab(tab);
|
|
const resourceWatcher = await createResourceWatcherForTarget(target);
|
|
return resourceWatcher;
|
|
}
|
|
|
|
async function createResourceWatcherForTarget(target) {
|
|
// Avoid mocha to try to load these module and fail while doing it when running node tests
|
|
const {
|
|
ResourceWatcher,
|
|
} = require("devtools/shared/resources/resource-watcher");
|
|
const { TargetList } = require("devtools/shared/resources/target-list");
|
|
|
|
const targetList = new TargetList(target.client.mainRoot, target);
|
|
await targetList.startListening();
|
|
return new ResourceWatcher(targetList);
|
|
}
|
|
|
|
// 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 (actor, 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.actor) {
|
|
res.actor = existingPacket.actor;
|
|
}
|
|
|
|
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) {
|
|
// Clean actor ids on each message.arguments item.
|
|
copyExistingActor(newArgument, existingArgument);
|
|
|
|
// `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.actor && existingPacket?.message?.actor) {
|
|
res.message.actor = existingPacket.message.actor;
|
|
}
|
|
|
|
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?.exception?.actor && existingPacket.exception.actor) {
|
|
// Clean actor ids on evaluation exception
|
|
copyExistingActor(res.exception, existingPacket.exception);
|
|
}
|
|
|
|
if (res.result && res.result._grip && existingPacket.result) {
|
|
// Clean actor ids on evaluation result messages.
|
|
copyExistingActor(res.result, existingPacket.result);
|
|
}
|
|
|
|
if (
|
|
res?.result?._grip?.promiseState?.reason &&
|
|
existingPacket?.result?._grip?.promiseState?.reason
|
|
) {
|
|
// Clean actor ids on evaluation promise result messages.
|
|
copyExistingActor(
|
|
res.result._grip.promiseState.reason,
|
|
existingPacket.result._grip.promiseState.reason
|
|
);
|
|
}
|
|
|
|
if (
|
|
res?.result?._grip?.promiseState?.timeToSettle &&
|
|
existingPacket?.result?._grip?.promiseState?.timeToSettle
|
|
) {
|
|
res.result._grip.promiseState.timeToSettle =
|
|
existingPacket.result._grip.promiseState.timeToSettle;
|
|
}
|
|
|
|
if (res.exception && existingPacket.exception) {
|
|
// Clean actor ids on exception messages.
|
|
copyExistingActor(res.exception, existingPacket.exception);
|
|
|
|
if (
|
|
res.exception._grip &&
|
|
res.exception._grip.preview &&
|
|
existingPacket.exception._grip &&
|
|
existingPacket.exception._grip.preview
|
|
) {
|
|
if (
|
|
typeof res.exception._grip.preview.message === "object" &&
|
|
res.exception._grip.preview.message._grip.type === "longString" &&
|
|
typeof existingPacket.exception._grip.preview.message === "object" &&
|
|
existingPacket.exception._grip.preview.message._grip.type ===
|
|
"longString"
|
|
) {
|
|
copyExistingActor(
|
|
res.exception._grip.preview.message,
|
|
existingPacket.exception._grip.preview.message
|
|
);
|
|
}
|
|
}
|
|
|
|
if (
|
|
typeof res.exceptionMessage === "object" &&
|
|
res.exceptionMessage._grip &&
|
|
res.exceptionMessage._grip.type === "longString"
|
|
) {
|
|
copyExistingActor(res.exceptionMessage, existingPacket.exceptionMessage);
|
|
}
|
|
}
|
|
|
|
if (res.eventActor) {
|
|
// Clean actor ids and startedDateTime on network messages.
|
|
res.eventActor.actor = existingPacket.actor;
|
|
res.eventActor.startedDateTime = existingPacket.startedDateTime;
|
|
}
|
|
|
|
if (res.pageError) {
|
|
// Clean innerWindowID on pageError messages.
|
|
res.pageError.innerWindowID = existingPacket.pageError.innerWindowID;
|
|
|
|
if (
|
|
typeof res.pageError.errorMessage === "object" &&
|
|
res.pageError.errorMessage._grip &&
|
|
res.pageError.errorMessage._grip.type === "longString"
|
|
) {
|
|
copyExistingActor(
|
|
res.pageError.errorMessage,
|
|
existingPacket.pageError.errorMessage
|
|
);
|
|
}
|
|
|
|
if (
|
|
res.pageError.exception?._grip?.preview?.message?._grip &&
|
|
existingPacket.pageError.exception?._grip?.preview?.message?._grip
|
|
) {
|
|
copyExistingActor(
|
|
res.pageError.exception._grip.preview.message,
|
|
existingPacket.pageError.exception._grip.preview.message
|
|
);
|
|
}
|
|
|
|
if (res.pageError.exception && existingPacket.pageError.exception) {
|
|
copyExistingActor(
|
|
res.pageError.exception,
|
|
existingPacket.pageError.exception
|
|
);
|
|
}
|
|
|
|
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];
|
|
if (frame && existingFrame && frame.sourceId) {
|
|
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;
|
|
}
|
|
|
|
if (res.actor && existingPacket.actor) {
|
|
res.actor = existingPacket.actor;
|
|
}
|
|
|
|
if (res.waitingTime && existingPacket.waitingTime) {
|
|
res.waitingTime = existingPacket.waitingTime;
|
|
}
|
|
|
|
if (res.helperResult) {
|
|
copyExistingActor(
|
|
res.helperResult.object,
|
|
existingPacket.helperResult.object
|
|
);
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
function copyExistingActor(a, b) {
|
|
if (!a || !b) {
|
|
return;
|
|
}
|
|
|
|
if (a.actorID && b.actorID) {
|
|
a.actorID = b.actorID;
|
|
}
|
|
|
|
if (a.actor && b.actor) {
|
|
a.actor = b.actor;
|
|
}
|
|
|
|
if (a._grip && b._grip && a._grip.actor && b._grip.actor) {
|
|
a._grip.actor = b._grip.actor;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Write stubs to a given file
|
|
*
|
|
* @param {Object} env
|
|
* @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(env, fileName, packets, isNetworkMessage) {
|
|
const mozRepo = 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. RUN TESTS IN FIXTURES/ TO UPDATE.
|
|
*/
|
|
|
|
const {
|
|
parsePacketsWithFronts,
|
|
} = require("chrome://mochitests/content/browser/devtools/client/webconsole/test/browser/stub-generator-helpers");
|
|
const { prepareMessage } = require("devtools/client/webconsole/utils/messages");
|
|
const {
|
|
ConsoleMessage,
|
|
NetworkEventMessage,
|
|
} = require("devtools/client/webconsole/types");
|
|
|
|
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 OS.File.writeAtomic(filePath, 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}
|
|
* - {Boolean} 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.
|
|
*/
|
|
function getSerializedPacket(packet, { sortKeys = false } = {}) {
|
|
if (sortKeys) {
|
|
packet = sortObjectKeys(packet);
|
|
}
|
|
|
|
return JSON.stringify(
|
|
packet,
|
|
function(_, value) {
|
|
// The message can have fronts that we need to serialize
|
|
if (value && value._grip) {
|
|
return { _grip: value._grip, actorID: value.actorID };
|
|
}
|
|
|
|
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,
|
|
createResourceWatcherForTab,
|
|
createResourceWatcherForTarget,
|
|
getStubFile,
|
|
getCleanedPacket,
|
|
getSerializedPacket,
|
|
parsePacketsWithFronts,
|
|
parsePacketAndCreateFronts,
|
|
writeStubsToFile,
|
|
};
|