/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ /* 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/. */ "use strict"; const WebConsoleUtils = require("devtools/client/webconsole/utils").Utils; const STRINGS_URI = "devtools/client/locales/webconsole.properties"; const l10n = new WebConsoleUtils.L10n(STRINGS_URI); const { MESSAGE_SOURCE, MESSAGE_TYPE, MESSAGE_LEVEL, } = require("../constants"); const { ConsoleMessage, NetworkEventMessage, } = require("../types"); function prepareMessage(packet, idGenerator) { // This packet is already in the expected packet structure. Simply return. if (!packet.source) { packet = transformPacket(packet); } if (packet.allowRepeating) { packet = packet.set("repeatId", getRepeatId(packet)); } return packet.set("id", idGenerator.getNextId()); } /** * Transforms a packet from Firefox RDP structure to Chrome RDP structure. */ function transformPacket(packet) { if (packet._type) { packet = convertCachedPacket(packet); } switch (packet.type) { case "consoleAPICall": { let { message } = packet; let parameters = message.arguments; let type = message.level; let level = getLevelFromType(type); let messageText = null; const timer = message.timer; // Special per-type conversion. switch (type) { case "clear": // We show a message to users when calls console.clear() is called. parameters = [l10n.getStr("consoleCleared")]; break; case "count": // Chrome RDP doesn't have a special type for count. type = MESSAGE_TYPE.LOG; let {counter} = message; let label = counter.label ? counter.label : l10n.getStr("noCounterLabel"); messageText = `${label}: ${counter.count}`; parameters = null; break; case "time": // We don't show anything for console.time calls to match Chrome's behaviour. parameters = null; type = MESSAGE_TYPE.NULL_MESSAGE; break; case "timeEnd": parameters = null; if (timer) { // We show the duration to users when calls console.timeEnd() is called, // if corresponding console.time() was called before. let duration = Math.round(timer.duration * 100) / 100; messageText = l10n.getFormatStr("timeEnd", [timer.name, duration]); } else { // If the `timer` property does not exists, we don't output anything. type = MESSAGE_TYPE.NULL_MESSAGE; } break; case "table": const supportedClasses = [ "Array", "Object", "Map", "Set", "WeakMap", "WeakSet"]; if ( !Array.isArray(parameters) || parameters.length === 0 || !supportedClasses.includes(parameters[0].class) ) { // 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; parameters = null; messageText = message.groupName || l10n.getStr("noGroupLabel"); break; case "groupCollapsed": type = MESSAGE_TYPE.START_GROUP_COLLAPSED; parameters = null; messageText = message.groupName || 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; } const frame = message.filename ? { source: message.filename, line: message.lineNumber, column: message.columnNumber, } : null; return new ConsoleMessage({ source: MESSAGE_SOURCE.CONSOLE_API, type, level, parameters, messageText, stacktrace: message.stacktrace ? message.stacktrace : null, frame, userProvidedStyles: message.styles, }); } case "navigationMessage": { let { message } = packet; return new ConsoleMessage({ source: MESSAGE_SOURCE.CONSOLE_API, type: MESSAGE_TYPE.LOG, level: MESSAGE_LEVEL.LOG, messageText: "Navigated to " + message.url, }); } case "pageError": { let { pageError } = packet; let level = MESSAGE_LEVEL.ERROR; if (pageError.warning || pageError.strict) { level = MESSAGE_LEVEL.WARN; } else if (pageError.info) { level = MESSAGE_LEVEL.INFO; } const frame = pageError.sourceName ? { source: pageError.sourceName, line: pageError.lineNumber, column: pageError.columnNumber } : null; return new ConsoleMessage({ source: MESSAGE_SOURCE.JAVASCRIPT, type: MESSAGE_TYPE.LOG, level, messageText: pageError.errorMessage, stacktrace: pageError.stacktrace ? pageError.stacktrace : null, frame, exceptionDocURL: pageError.exceptionDocURL, notes: pageError.notes, }); } case "networkEvent": { let { networkEvent } = packet; return new NetworkEventMessage({ actor: networkEvent.actor, isXHR: networkEvent.isXHR, request: networkEvent.request, response: networkEvent.response, }); } case "evaluationResult": default: { let { exceptionMessage: messageText, exceptionDocURL, frame, result: parameters, notes, } = packet; const level = messageText ? MESSAGE_LEVEL.ERROR : MESSAGE_LEVEL.LOG; return new ConsoleMessage({ source: MESSAGE_SOURCE.JAVASCRIPT, type: MESSAGE_TYPE.RESULT, level, messageText, parameters, exceptionDocURL, frame, notes, }); } } } // Helpers function getRepeatId(message) { message = message.toJS(); delete message.repeat; return JSON.stringify(message); } function convertCachedPacket(packet) { // The devtools server provides cached message packets in a different shape, so we // transform them here. let convertPacket = {}; if (packet._type === "ConsoleAPI") { convertPacket.message = packet; convertPacket.type = "consoleAPICall"; } else if (packet._type === "PageError") { convertPacket.pageError = packet; convertPacket.type = "pageError"; } else if ("_navPayload" in packet) { convertPacket.type = "navigationMessage"; convertPacket.message = packet; } else if (packet._type === "NetworkEvent") { convertPacket.networkEvent = packet; convertPacket.type = "networkEvent"; } else { throw new Error("Unexpected packet type"); } return convertPacket; } /** * 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, 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_LOG, 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_DEBUG, }; return levelMap[type] || MESSAGE_TYPE.LOG; } function isGroupType(type) { return [ MESSAGE_TYPE.START_GROUP, MESSAGE_TYPE.START_GROUP_COLLAPSED ].includes(type); } exports.prepareMessage = prepareMessage; // Export for use in testing. exports.getRepeatId = getRepeatId; exports.l10n = l10n; exports.isGroupType = isGroupType;