/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// URL Regex, common idioms: // // Lead-in (URL): // ( Capture because we need to know if there was a lead-in // character so we can include it as part of the text // preceding the match. We lack look-behind matching. // ^| The URL can start at the beginning of the string. // [\s(,;'"`“] Or whitespace or some punctuation that does not imply // a context which would preclude a URL. // ) // // We do not need a trailing look-ahead because our regex's will terminate // because they run out of characters they can eat.
// What we do not attempt to have the regexp do: // - Avoid trailing '.' and ')' characters. We let our greedy match absorb // these, but have a separate regex for extra characters to leave off at the // end. // // The Regex (apart from lead-in/lead-out): // ( Begin capture of the URL // (?: (potential detect beginnings) // https?:\/\/| Start with "http" or "https" // www\d{0,3}[.][a-z0-9.\-]{2,249}| // Start with "www", up to 3 numbers, then "." then // something that looks domain-namey. We differ from the // next case in that we do not constrain the top-level // domain as tightly and do not require a trailing path // indicator of "/". This is IDN root compatible. // [a-z0-9.\-]{2,250}[.][a-z]{2,4}\/ // Detect a non-www domain, but requiring a trailing "/" // to indicate a path. This only detects IDN domains // with a non-IDN root. This is reasonable in cases where // there is no explicit http/https start us out, but // unreasonable where there is. Our real fix is the bug // to port the Thunderbird/gecko linkification logic. // // Domain names can be up to 253 characters long, and are // limited to a-zA-Z0-9 and '-'. The roots don't have // hyphens unless they are IDN roots. Root zones can be // found here: http://www.iana.org/domains/root/db // ) // [-\w.!~*'();,/?:@&=+$#%]* // path onwards. We allow the set of characters that // encodeURI does not escape plus the result of escaping // (so also '%') // ) // eslint-disable-next-line max-len const urlRegex =
/(^|[\s(,;'"`“])((?:https?:\/\/|www\d{0,3}[.][a-z0-9.\-]{2,249}|[a-z0-9.\-]{2,250}[.][a-z]{2,4}\/)[-\w.!~*'();,/?:@&=+$#%]*)/im;
// Set of terminators that are likely to have been part of the context rather // than part of the URL and so should be uneaten. This is '(', ',', ';', plus // quotes and question end-ing punctuation and the potential permutations with // parentheses (english-specific). const uneatLastUrlCharsRegex = /(?:[),;.!?`'"]|[.!?]\)|\)[.!?])$/;
function prepareMessage(resource, idGenerator, persistLogs) { if (!resource.source) {
resource = transformResource(resource, persistLogs);
}
// The Tracer resource transformer may process some resource // which aren't translated into any item in the console (Tracer frames) if (resource) {
resource.id = idGenerator.getNextId(resource);
} return resource;
}
/** * Transforms a resource given its type. * * @param {Object} resource: This can be either a simple RDP packet or an object emitted * by the Resource API. * @param {Boolean} persistLogs: Value of the "Persist logs" setting
*/ function transformResource(resource, persistLogs) { switch (resource.resourceType || resource.type) { case ResourceCommand.TYPES.CONSOLE_MESSAGE: { return transformConsoleAPICallResource(
resource,
persistLogs,
resource.targetFront
);
}
case ResourceCommand.TYPES.PLATFORM_MESSAGE: { return transformPlatformMessageResource(resource);
}
case ResourceCommand.TYPES.ERROR_MESSAGE: { return transformPageErrorResource(resource);
}
case ResourceCommand.TYPES.CSS_MESSAGE: { return transformCSSMessageResource(resource);
}
case ResourceCommand.TYPES.NETWORK_EVENT: { return transformNetworkEventResource(resource);
}
case ResourceCommand.TYPES.JSTRACER_STATE: { return transformTracerStateResource(resource);
}
case ResourceCommand.TYPES.JSTRACER_TRACE: { return transformTraceResource(resource);
}
// eslint-disable-next-line complexity function transformConsoleAPICallResource(
consoleMessageResource,
persistLogs,
targetFront
) {
let { arguments: parameters, level: type, timer } = consoleMessageResource;
let level = getLevelFromType(type);
let messageText = null;
// Special per-type conversion. switch (type) { case"clear": // We show a message to users when calls console.clear() is called.
parameters = [
l10n.getStr(persistLogs ? "preventedConsoleClear" : "consoleCleared"),
]; break; case"count": case"countReset": // Chrome RDP doesn't have a special type for count.
type = MESSAGE_TYPE.LOG; const { counter } = consoleMessageResource;
if (!counter) { // We don't show anything if we don't have counter data.
type = MESSAGE_TYPE.NULL_MESSAGE;
} elseif (counter.error) {
messageText = l10n.getFormatStr(counter.error, [counter.label]);
level = MESSAGE_LEVEL.WARN;
parameters = null;
} else { const label = counter.label
? counter.label
: l10n.getStr("noCounterLabel");
messageText = `${label}: ${counter.count}`;
parameters = null;
} break; case"timeStamp":
type = MESSAGE_TYPE.NULL_MESSAGE; break; case"time":
parameters = null; if (timer && timer.error) {
messageText = l10n.getFormatStr(timer.error, [timer.name]);
level = MESSAGE_LEVEL.WARN;
} else { // We don't show anything for console.time calls to match Chrome's behaviour.
type = MESSAGE_TYPE.NULL_MESSAGE;
} break; case"timeLog": case"timeEnd": if (timer && timer.error) {
parameters = null;
messageText = l10n.getFormatStr(timer.error, [timer.name]);
level = MESSAGE_LEVEL.WARN;
} elseif (timer) { // We show the duration to users when calls console.timeLog/timeEnd is called, // if corresponding console.time() was called before. const duration = Math.round(timer.duration * 100) / 100; if (type === "timeEnd") {
messageText = l10n.getFormatStr("console.timeEnd", [
timer.name,
duration,
]);
parameters = null;
} elseif (type === "timeLog") { const [, ...rest] = parameters;
parameters = [
l10n.getFormatStr("timeLog", [timer.name, duration]),
...rest,
];
}
} else { // If the `timer` property does not exists, we don't output anything.
type = MESSAGE_TYPE.NULL_MESSAGE;
} break; case"table": if (!isSupportedByConsoleTable(parameters)) { // If the class of the first parameter is not supported, // we handle the call as a simple console.log
type = "log";
} break; case"group":
type = MESSAGE_TYPE.START_GROUP; if (parameters.length === 0) {
parameters = [l10n.getStr("noGroupLabel")];
} break; case"groupCollapsed":
type = MESSAGE_TYPE.START_GROUP_COLLAPSED; if (parameters.length === 0) {
parameters = [l10n.getStr("noGroupLabel")];
} break; case"groupEnd":
type = MESSAGE_TYPE.END_GROUP;
parameters = null; break; case"dirxml": // Handle console.dirxml calls as simple console.log
type = "log"; break;
}
function transformEvaluationResultPacket(packet) {
let {
exceptionMessage,
errorMessageName,
exceptionDocURL,
exception,
exceptionStack,
hasException,
frame,
result,
helperResult,
timestamp: timeStamp,
notes,
} = packet;
let parameter;
if (hasException) { // If we have an exception, we prefix it, and we reset the exception message, as we're // not going to use it.
parameter = exception;
exceptionMessage = null;
} elseif (helperResult?.object) {
parameter = helperResult.object;
} elseif (helperResult?.type === "error") { try {
exceptionMessage = l10n.getFormatStr(
helperResult.message,
helperResult.messageArgs || []
);
} catch (ex) {
exceptionMessage = helperResult.message;
}
} else {
parameter = result;
}
/** * Return if passed messages are similar and can thus be "repeated". * ⚠ This function is on a hot path, called for (almost) every message being sent by * the server. This should be kept as fast as possible. * * @param {Message} message1 * @param {Message} message2 * @returns {Boolean}
*/ // eslint-disable-next-line complexity function areMessagesSimilar(message1, message2) { if (!message1 || !message2) { returnfalse;
}
if (!areMessagesParametersSimilar(message1, message2)) { returnfalse;
}
if (!areMessagesStacktracesSimilar(message1, message2)) { returnfalse;
}
/** * Return if passed messages parameters are similar * ⚠ This function is on a hot path, called for (almost) every message being sent by * the server. This should be kept as fast as possible. * * @param {Message} message1 * @param {Message} message2 * @returns {Boolean}
*/ function areMessagesParametersSimilar(message1, message2) { const message1ParamsLength = message1.parameters?.length; if (message1ParamsLength !== message2.parameters?.length) { returnfalse;
}
if (!message1ParamsLength) { returntrue;
}
for (let i = 0; i < message1ParamsLength; i++) { const message1Parameter = message1.parameters[i]; const message2Parameter = message2.parameters[i]; // exceptions have a grip, but we want to consider 2 messages similar as long as // they refer to the same error. if (
message1.hasException &&
message2.hasException &&
message1Parameter._grip?.class == message2Parameter._grip?.class &&
message1Parameter._grip?.preview?.message ==
message2Parameter._grip?.preview?.message &&
message1Parameter._grip?.preview?.stack ==
message2Parameter._grip?.preview?.stack
) { continue;
}
// For object references (grips), that are not exceptions, we don't want to consider // messages to be the same as we only have a preview of what they look like, and not // some kind of property that would give us the state of a given instance at a given // time. if (message1Parameter._grip || message2Parameter._grip) { returnfalse;
}
if (message1Parameter.type !== message2Parameter.type) { returnfalse;
}
if (message1Parameter.type) { if (message1Parameter.text !== message2Parameter.text) { returnfalse;
} // Some objects don't have a text property but a name one (e.g. Symbol) if (message1Parameter.name !== message2Parameter.name) { returnfalse;
}
} elseif (message1Parameter !== message2Parameter) { returnfalse;
}
} returntrue;
}
/** * Return if passed messages stacktraces are similar * * @param {Message} message1 * @param {Message} message2 * @returns {Boolean}
*/ function areMessagesStacktracesSimilar(message1, message2) { const message1StackLength = message1.stacktrace?.length; if (message1StackLength !== message2.stacktrace?.length) { returnfalse;
}
if (!message1StackLength) { returntrue;
}
for (let i = 0; i < message1StackLength; i++) { const message1Frame = message1.stacktrace[i]; const message2Frame = message2.stacktrace[i];
if (message1Frame.filename !== message2Frame.filename) { returnfalse;
}
if (message1Frame.columnNumber !== message2Frame.columnNumber) { returnfalse;
}
if (message1Frame.lineNumber !== message2Frame.lineNumber) { returnfalse;
}
} returntrue;
}
/** * Maps a Firefox RDP type to its corresponding level.
*/ function getLevelFromType(type) { const levels = {
LEVEL_ERROR: "error",
LEVEL_WARNING: "warn",
LEVEL_INFO: "info",
LEVEL_LOG: "log",
LEVEL_DEBUG: "debug",
};
// A mapping from the console API log event levels to the Web Console levels. const levelMap = {
error: levels.LEVEL_ERROR,
exception: levels.LEVEL_ERROR, assert: levels.LEVEL_ERROR,
logPointError: levels.LEVEL_ERROR,
warn: levels.LEVEL_WARNING,
info: levels.LEVEL_INFO,
log: levels.LEVEL_LOG,
clear: levels.LEVEL_LOG,
trace: levels.LEVEL_LOG,
table: levels.LEVEL_LOG,
debug: levels.LEVEL_DEBUG,
dir: levels.LEVEL_LOG,
dirxml: levels.LEVEL_LOG,
group: levels.LEVEL_LOG,
groupCollapsed: levels.LEVEL_LOG,
groupEnd: levels.LEVEL_LOG,
time: levels.LEVEL_LOG,
timeEnd: levels.LEVEL_LOG,
count: levels.LEVEL_LOG,
};
return levelMap[type] || MESSAGE_TYPE.LOG;
}
function isGroupType(type) { return [
MESSAGE_TYPE.START_GROUP,
MESSAGE_TYPE.START_GROUP_COLLAPSED,
].includes(type);
}
/** * Given the a regular warning message, compute the label of the warning group the message * could be in. * For example, if the message text is: * The resource at “http://evil.com” was blocked because content blocking is enabled * * it may be turned into * * The resource at “<URL>” was blocked because content blocking is enabled * * @param {ConsoleMessage} firstMessage * @returns {String} The computed label
*/ function getWarningGroupLabel(firstMessage) { if (
isContentBlockingMessage(firstMessage) ||
isStorageIsolationMessage(firstMessage) ||
isTrackingProtectionMessage(firstMessage)
) { return replaceURL(firstMessage.messageText, "");
}
if (isCookieMessage(firstMessage)) { return l10n.getStr("webconsole.group.cookie");
}
if (isCSPMessage(firstMessage)) { return l10n.getStr("webconsole.group.csp");
}
return"";
}
/** * Replace any URL in the provided text by the provided replacement text, or an empty * string. * * @param {String} text * @param {String} replacementText * @returns {String}
*/ function replaceURL(text, replacementText = "") {
let result = "";
let currentIndex = 0;
let contentStart; while (true) { const url = urlRegex.exec(text); // Pick the regexp with the earlier content; index will always be zero. if (!url) { break;
}
contentStart = url.index + url[1].length; if (contentStart > 0) { const nonUrlText = text.substring(0, contentStart);
result += nonUrlText;
}
// There are some final characters for a URL that are much more likely // to have been part of the enclosing text rather than the end of the // URL.
let useUrl = url[2]; const uneat = uneatLastUrlCharsRegex.exec(useUrl); if (uneat) {
useUrl = useUrl.substring(0, uneat.index);
}
/** * Get the warningGroup type in which the message could be in. * @param {ConsoleMessage} message * @returns {String|null} null if the message can't be part of a warningGroup.
*/ function getWarningGroupType(message) { // We got report that this can be called with `undefined` (See Bug 1801462 and Bug 1810109). // Until we manage to reproduce and find why this happens, guard on message so at least // we don't crash the console. if (!message) { returnnull;
}
if (
message.level !== MESSAGE_LEVEL.WARN && // Cookie messages are both warnings and infos
message.level !== MESSAGE_LEVEL.INFO
) { returnnull;
}
if (isContentBlockingMessage(message)) { return MESSAGE_TYPE.CONTENT_BLOCKING_GROUP;
}
if (isStorageIsolationMessage(message)) { return MESSAGE_TYPE.STORAGE_ISOLATION_GROUP;
}
if (isTrackingProtectionMessage(message)) { return MESSAGE_TYPE.TRACKING_PROTECTION_GROUP;
}
if (isCookieMessage(message)) { return MESSAGE_TYPE.COOKIE_GROUP;
}
if (isCSPMessage(message)) { return MESSAGE_TYPE.CSP_GROUP;
}
returnnull;
}
/** * Returns a computed id given a message * * @param {ConsoleMessage} type: the message type, from MESSAGE_TYPE. * @param {Integer} innerWindowID: the message innerWindowID. * @returns {String}
*/ function getParentWarningGroupMessageId(message) { const warningGroupType = getWarningGroupType(message); if (!warningGroupType) { returnnull;
}
// It can happen that messages are emitted in the same microsecond, making their // timestamp similar. In such case, we rely on which message came first through // the console API service, checking their id, except for expression result, which we'll // always insert after because console API messages emitted from the expression need to // be rendered before. if (messageA.timeStamp === messageB.timeStamp) { if (messageA.type === "result") { return bFirst;
}
if (messageB.type === "result") { return aFirst;
}
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.65Bemerkung:
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.