debug-server-next
Version:
Dev server for hippy-core.
585 lines (584 loc) • 26 kB
JavaScript
/*
* Copyright (C) 2011 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import * as Common from '../common/common.js';
import * as Host from '../host/host.js';
import * as i18n from '../i18n/i18n.js';
import { FrontendMessageSource, FrontendMessageType } from './ConsoleModelTypes.js';
export { FrontendMessageSource, FrontendMessageType } from './ConsoleModelTypes.js';
import { CPUProfilerModel, Events as CPUProfilerModelEvents } from './CPUProfilerModel.js';
import { Events as DebuggerModelEvents } from './DebuggerModel.js';
import { LogModel } from './LogModel.js';
import { RemoteObject } from './RemoteObject.js';
import { Events as ResourceTreeModelEvents, ResourceTreeModel } from './ResourceTreeModel.js';
import { Events as RuntimeModelEvents, RuntimeModel } from './RuntimeModel.js';
import { TargetManager } from './TargetManager.js';
const UIStrings = {
/**
*@description Text shown when the main frame (page) of the website was navigated to a different URL.
*@example {https://example.com} PH1
*/
navigatedToS: 'Navigated to {PH1}',
/**
*@description Text shown in the console when a performance profile (with the given name) was started.
*@example {title} PH1
*/
profileSStarted: 'Profile \'{PH1}\' started.',
/**
*@description Text shown in the console when a performance profile (with the given name) was stopped.
*@example {name} PH1
*/
profileSFinished: 'Profile \'{PH1}\' finished.',
/**
*@description Error message shown in the console after the user tries to save a JavaScript value to a temporary variable.
*/
failedToSaveToTempVariable: 'Failed to save to temp variable.',
};
const str_ = i18n.i18n.registerUIStrings('core/sdk/ConsoleModel.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
let settingsInstance;
export class ConsoleModel extends Common.ObjectWrapper.ObjectWrapper {
_messages;
_messageByExceptionId;
_warnings;
_errors;
_violations;
pageLoadSequenceNumber;
_targetListeners;
constructor() {
super();
this._messages = [];
this._messageByExceptionId = new Map();
this._warnings = 0;
this._errors = 0;
this._violations = 0;
this.pageLoadSequenceNumber = 0;
this._targetListeners = new WeakMap();
TargetManager.instance().observeTargets(this);
}
static instance(opts = { forceNew: null }) {
const { forceNew } = opts;
if (!settingsInstance || forceNew) {
settingsInstance = new ConsoleModel();
}
return settingsInstance;
}
targetAdded(target) {
const resourceTreeModel = target.model(ResourceTreeModel);
if (!resourceTreeModel || resourceTreeModel.cachedResourcesLoaded()) {
this._initTarget(target);
return;
}
const eventListener = resourceTreeModel.addEventListener(ResourceTreeModelEvents.CachedResourcesLoaded, () => {
Common.EventTarget.removeEventListeners([eventListener]);
this._initTarget(target);
});
}
_initTarget(target) {
const eventListeners = [];
const cpuProfilerModel = target.model(CPUProfilerModel);
if (cpuProfilerModel) {
eventListeners.push(cpuProfilerModel.addEventListener(CPUProfilerModelEvents.ConsoleProfileStarted, this._consoleProfileStarted.bind(this, cpuProfilerModel)));
eventListeners.push(cpuProfilerModel.addEventListener(CPUProfilerModelEvents.ConsoleProfileFinished, this._consoleProfileFinished.bind(this, cpuProfilerModel)));
}
const resourceTreeModel = target.model(ResourceTreeModel);
if (resourceTreeModel && !target.parentTarget()) {
eventListeners.push(resourceTreeModel.addEventListener(ResourceTreeModelEvents.MainFrameNavigated, this._mainFrameNavigated, this));
}
const runtimeModel = target.model(RuntimeModel);
if (runtimeModel) {
eventListeners.push(runtimeModel.addEventListener(RuntimeModelEvents.ExceptionThrown, this._exceptionThrown.bind(this, runtimeModel)));
eventListeners.push(runtimeModel.addEventListener(RuntimeModelEvents.ExceptionRevoked, this._exceptionRevoked.bind(this, runtimeModel)));
eventListeners.push(runtimeModel.addEventListener(RuntimeModelEvents.ConsoleAPICalled, this._consoleAPICalled.bind(this, runtimeModel)));
if (!target.parentTarget()) {
eventListeners.push(runtimeModel.debuggerModel().addEventListener(DebuggerModelEvents.GlobalObjectCleared, this._clearIfNecessary, this));
}
eventListeners.push(runtimeModel.addEventListener(RuntimeModelEvents.QueryObjectRequested, this._queryObjectRequested.bind(this, runtimeModel)));
}
this._targetListeners.set(target, eventListeners);
}
targetRemoved(target) {
const runtimeModel = target.model(RuntimeModel);
if (runtimeModel) {
this._messageByExceptionId.delete(runtimeModel);
}
Common.EventTarget.removeEventListeners(this._targetListeners.get(target) || []);
}
async evaluateCommandInConsole(executionContext, originatingMessage, expression, useCommandLineAPI) {
const result = await executionContext.evaluate({
expression: expression,
objectGroup: 'console',
includeCommandLineAPI: useCommandLineAPI,
silent: false,
returnByValue: false,
generatePreview: true,
replMode: true,
allowUnsafeEvalBlockedByCSP: false,
disableBreaks: undefined,
throwOnSideEffect: undefined,
timeout: undefined,
}, Common.Settings.Settings.instance().moduleSetting('consoleUserActivationEval').get(), /* awaitPromise */ false);
Host.userMetrics.actionTaken(Host.UserMetrics.Action.ConsoleEvaluated);
if ('error' in result) {
return;
}
await Common.Console.Console.instance().showPromise();
this.dispatchEventToListeners(Events.CommandEvaluated, { result: result.object, commandMessage: originatingMessage, exceptionDetails: result.exceptionDetails });
}
addCommandMessage(executionContext, text) {
const commandMessage = new ConsoleMessage(executionContext.runtimeModel, "javascript" /* Javascript */, null, text, { type: FrontendMessageType.Command });
commandMessage.setExecutionContextId(executionContext.id);
this.addMessage(commandMessage);
return commandMessage;
}
addMessage(msg) {
msg.setPageLoadSequenceNumber(this.pageLoadSequenceNumber);
if (msg.source === FrontendMessageSource.ConsoleAPI &&
msg.type === "clear" /* Clear */) {
this._clearIfNecessary();
}
this._messages.push(msg);
const runtimeModel = msg.runtimeModel();
const exceptionId = msg.getExceptionId();
if (exceptionId && runtimeModel) {
let modelMap = this._messageByExceptionId.get(runtimeModel);
if (!modelMap) {
modelMap = new Map();
this._messageByExceptionId.set(runtimeModel, modelMap);
}
modelMap.set(exceptionId, msg);
}
this._incrementErrorWarningCount(msg);
this.dispatchEventToListeners(Events.MessageAdded, msg);
}
_exceptionThrown(runtimeModel, event) {
const exceptionWithTimestamp = event.data;
const affectedResources = extractExceptionMetaData(exceptionWithTimestamp.details.exceptionMetaData);
const consoleMessage = ConsoleMessage.fromException(runtimeModel, exceptionWithTimestamp.details, undefined, exceptionWithTimestamp.timestamp, undefined, affectedResources);
consoleMessage.setExceptionId(exceptionWithTimestamp.details.exceptionId);
this.addMessage(consoleMessage);
}
_exceptionRevoked(runtimeModel, event) {
const exceptionId = event.data;
const modelMap = this._messageByExceptionId.get(runtimeModel);
const exceptionMessage = modelMap ? modelMap.get(exceptionId) : null;
if (!exceptionMessage) {
return;
}
this._errors--;
exceptionMessage.level = "verbose" /* Verbose */;
this.dispatchEventToListeners(Events.MessageUpdated, exceptionMessage);
}
_consoleAPICalled(runtimeModel, event) {
const call = event.data;
let level = "info" /* Info */;
if (call.type === "debug" /* Debug */) {
level = "verbose" /* Verbose */;
}
else if (call.type === "error" /* Error */ ||
call.type === "assert" /* Assert */) {
level = "error" /* Error */;
}
else if (call.type === "warning" /* Warning */) {
level = "warning" /* Warning */;
}
else if (call.type === "info" /* Info */ ||
call.type === "log" /* Log */) {
level = "info" /* Info */;
}
let message = '';
if (call.args.length && call.args[0].unserializableValue) {
message = call.args[0].unserializableValue;
}
else if (call.args.length && (typeof call.args[0].value !== 'object' || call.args[0].value === null)) {
message = String(call.args[0].value);
}
else if (call.args.length && call.args[0].description) {
message = call.args[0].description;
}
const callFrame = call.stackTrace && call.stackTrace.callFrames.length ? call.stackTrace.callFrames[0] : null;
const details = {
type: call.type,
url: callFrame?.url,
line: callFrame?.lineNumber,
column: callFrame?.columnNumber,
parameters: call.args,
stackTrace: call.stackTrace,
timestamp: call.timestamp,
executionContextId: call.executionContextId,
context: call.context,
};
const consoleMessage = new ConsoleMessage(runtimeModel, FrontendMessageSource.ConsoleAPI, level, message, details);
this.addMessage(consoleMessage);
}
_queryObjectRequested(runtimeModel, event) {
const { objects, executionContextId } = event.data;
const details = {
type: FrontendMessageType.QueryObjectResult,
parameters: [objects],
executionContextId,
};
const consoleMessage = new ConsoleMessage(runtimeModel, FrontendMessageSource.ConsoleAPI, "info" /* Info */, '', details);
this.addMessage(consoleMessage);
}
_clearIfNecessary() {
if (!Common.Settings.Settings.instance().moduleSetting('preserveConsoleLog').get()) {
this._clear();
}
++this.pageLoadSequenceNumber;
}
_mainFrameNavigated(event) {
if (Common.Settings.Settings.instance().moduleSetting('preserveConsoleLog').get()) {
Common.Console.Console.instance().log(i18nString(UIStrings.navigatedToS, { PH1: event.data.url }));
}
}
_consoleProfileStarted(cpuProfilerModel, event) {
const data = event.data;
this._addConsoleProfileMessage(cpuProfilerModel, "profile" /* Profile */, data.scriptLocation, i18nString(UIStrings.profileSStarted, { PH1: data.title }));
}
_consoleProfileFinished(cpuProfilerModel, event) {
const data = event.data;
this._addConsoleProfileMessage(cpuProfilerModel, "profileEnd" /* ProfileEnd */, data.scriptLocation, i18nString(UIStrings.profileSFinished, { PH1: data.title }));
}
_addConsoleProfileMessage(cpuProfilerModel, type, scriptLocation, messageText) {
const script = scriptLocation.script();
const callFrames = [{
functionName: '',
scriptId: scriptLocation.scriptId,
url: script ? script.contentURL() : '',
lineNumber: scriptLocation.lineNumber,
columnNumber: scriptLocation.columnNumber || 0,
}];
this.addMessage(new ConsoleMessage(cpuProfilerModel.runtimeModel(), FrontendMessageSource.ConsoleAPI, "info" /* Info */, messageText, { type, stackTrace: { callFrames } }));
}
_incrementErrorWarningCount(msg) {
if (msg.source === "violation" /* Violation */) {
this._violations++;
return;
}
switch (msg.level) {
case "warning" /* Warning */:
this._warnings++;
break;
case "error" /* Error */:
this._errors++;
break;
}
}
messages() {
return this._messages;
}
requestClearMessages() {
for (const logModel of TargetManager.instance().models(LogModel)) {
logModel.requestClear();
}
for (const runtimeModel of TargetManager.instance().models(RuntimeModel)) {
runtimeModel.discardConsoleEntries();
}
this._clear();
}
_clear() {
this._messages = [];
this._messageByExceptionId.clear();
this._errors = 0;
this._warnings = 0;
this._violations = 0;
this.dispatchEventToListeners(Events.ConsoleCleared);
}
errors() {
return this._errors;
}
warnings() {
return this._warnings;
}
violations() {
return this._violations;
}
async saveToTempVariable(currentExecutionContext, remoteObject) {
if (!remoteObject || !currentExecutionContext) {
failedToSave(null);
return;
}
const executionContext = currentExecutionContext;
const result = await executionContext.globalObject(/* objectGroup */ '', /* generatePreview */ false);
if ('error' in result || Boolean(result.exceptionDetails) || !result.object) {
failedToSave('object' in result && result.object || null);
return;
}
const globalObject = result.object;
const callFunctionResult =
// TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
// @ts-expect-error
await globalObject.callFunction(saveVariable, [RemoteObject.toCallArgument(remoteObject)]);
globalObject.release();
if (callFunctionResult.wasThrown || !callFunctionResult.object || callFunctionResult.object.type !== 'string') {
failedToSave(callFunctionResult.object || null);
}
else {
const text = callFunctionResult.object.value;
const message = this.addCommandMessage(executionContext, text);
this.evaluateCommandInConsole(executionContext, message, text, /* useCommandLineAPI */ false);
}
if (callFunctionResult.object) {
callFunctionResult.object.release();
}
function saveVariable(value) {
const prefix = 'temp';
let index = 1;
while ((prefix + index) in this) {
++index;
}
const name = prefix + index;
// @ts-ignore Assignment to global object
this[name] = value;
return name;
}
function failedToSave(result) {
let message = i18nString(UIStrings.failedToSaveToTempVariable);
if (result) {
message = message + ' ' + result.description;
}
Common.Console.Console.instance().error(message);
}
}
}
// TODO(crbug.com/1167717): Make this a const enum again
// eslint-disable-next-line rulesdir/const_enum
export var Events;
(function (Events) {
Events["ConsoleCleared"] = "ConsoleCleared";
Events["MessageAdded"] = "MessageAdded";
Events["MessageUpdated"] = "MessageUpdated";
Events["CommandEvaluated"] = "CommandEvaluated";
})(Events || (Events = {}));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function extractExceptionMetaData(metaData) {
if (!metaData) {
return undefined;
}
return { requestId: metaData.requestId || undefined, issueId: metaData.issueId || undefined };
}
function areAffectedResourcesEquivalent(a, b) {
// Not considering issueId, as that would prevent de-duplication of console messages.
return a?.requestId === b?.requestId;
}
export class ConsoleMessage {
_runtimeModel;
source;
level;
messageText;
type;
url;
line;
column;
parameters;
stackTrace;
timestamp;
executionContextId;
scriptId;
workerId;
context;
originatingConsoleMessage = null;
pageLoadSequenceNumber = undefined;
exceptionId = undefined;
affectedResources;
constructor(runtimeModel, source, level, messageText, details) {
this._runtimeModel = runtimeModel;
this.source = source;
this.level = level;
this.messageText = messageText;
this.type = details?.type || "log" /* Log */;
this.url = details?.url;
this.line = details?.line || 0;
this.column = details?.column || 0;
this.parameters = details?.parameters;
this.stackTrace = details?.stackTrace;
this.timestamp = details?.timestamp || Date.now();
this.executionContextId = details?.executionContextId || 0;
this.scriptId = details?.scriptId;
this.workerId = details?.workerId;
this.affectedResources = details?.affectedResources;
if (!this.executionContextId && this._runtimeModel) {
if (this.scriptId) {
this.executionContextId = this._runtimeModel.executionContextIdForScriptId(this.scriptId);
}
else if (this.stackTrace) {
this.executionContextId = this._runtimeModel.executionContextForStackTrace(this.stackTrace);
}
}
if (details?.context) {
const match = details?.context.match(/[^#]*/);
this.context = match?.[0];
}
}
getAffectedResources() {
return this.affectedResources;
}
setPageLoadSequenceNumber(pageLoadSequenceNumber) {
this.pageLoadSequenceNumber = pageLoadSequenceNumber;
}
static fromException(runtimeModel, exceptionDetails, messageType, timestamp, forceUrl, affectedResources) {
const details = {
type: messageType,
url: forceUrl || exceptionDetails.url,
line: exceptionDetails.lineNumber,
column: exceptionDetails.columnNumber,
parameters: exceptionDetails.exception ?
[RemoteObject.fromLocalObject(exceptionDetails.text), exceptionDetails.exception] :
undefined,
stackTrace: exceptionDetails.stackTrace,
timestamp,
executionContextId: exceptionDetails.executionContextId,
scriptId: exceptionDetails.scriptId,
affectedResources,
};
return new ConsoleMessage(runtimeModel, "javascript" /* Javascript */, "error" /* Error */, RuntimeModel.simpleTextFromException(exceptionDetails), details);
}
runtimeModel() {
return this._runtimeModel;
}
target() {
return this._runtimeModel ? this._runtimeModel.target() : null;
}
setOriginatingMessage(originatingMessage) {
this.originatingConsoleMessage = originatingMessage;
this.executionContextId = originatingMessage.executionContextId;
}
originatingMessage() {
return this.originatingConsoleMessage;
}
setExecutionContextId(executionContextId) {
this.executionContextId = executionContextId;
}
getExecutionContextId() {
return this.executionContextId;
}
getExceptionId() {
return this.exceptionId;
}
setExceptionId(exceptionId) {
this.exceptionId = exceptionId;
}
isGroupMessage() {
return this.type === "startGroup" /* StartGroup */ ||
this.type === "startGroupCollapsed" /* StartGroupCollapsed */ ||
this.type === "endGroup" /* EndGroup */;
}
isGroupStartMessage() {
return this.type === "startGroup" /* StartGroup */ ||
this.type === "startGroupCollapsed" /* StartGroupCollapsed */;
}
isErrorOrWarning() {
return (this.level === "warning" /* Warning */ || this.level === "error" /* Error */);
}
isGroupable() {
const isUngroupableError = this.level === "error" /* Error */ &&
(this.source === "javascript" /* Javascript */ || this.source === "network" /* Network */);
return (this.source !== FrontendMessageSource.ConsoleAPI && this.type !== FrontendMessageType.Command &&
this.type !== FrontendMessageType.Result && this.type !== FrontendMessageType.System && !isUngroupableError);
}
groupCategoryKey() {
return [this.source, this.level, this.type, this.pageLoadSequenceNumber].join(':');
}
isEqual(msg) {
if (!msg) {
return false;
}
if (!this.isEqualStackTraces(this.stackTrace, msg.stackTrace)) {
return false;
}
if (this.parameters) {
if (!msg.parameters || this.parameters.length !== msg.parameters.length) {
return false;
}
for (let i = 0; i < msg.parameters.length; ++i) {
const msgParam = msg.parameters[i];
const param = this.parameters[i];
if (typeof msgParam === 'string' || typeof param === 'string') {
// TODO(chromium:1136435): Remove this case.
return false;
}
// Never treat objects as equal - their properties might change over time. Errors can be treated as equal
// since they are always formatted as strings.
if (msgParam.type === 'object' && msgParam.subtype !== 'error') {
return false;
}
if (param.type !== msgParam.type || param.value !== msgParam.value ||
param.description !== msgParam.description) {
return false;
}
}
}
const watchExpressionRegex = /^watch-expression-\d+.devtools$/;
const bothAreWatchExpressions = watchExpressionRegex.test(this.url || '') && watchExpressionRegex.test(msg.url || '');
return (this.runtimeModel() === msg.runtimeModel()) && (this.source === msg.source) && (this.type === msg.type) &&
(this.level === msg.level) && (this.line === msg.line) && (this.url === msg.url) &&
(bothAreWatchExpressions || this.scriptId === msg.scriptId) && (this.messageText === msg.messageText) &&
(this.executionContextId === msg.executionContextId) &&
areAffectedResourcesEquivalent(this.affectedResources, msg.affectedResources);
}
isEqualStackTraces(stackTrace1, stackTrace2) {
if (!stackTrace1 !== !stackTrace2) {
return false;
}
if (!stackTrace1 || !stackTrace2) {
return true;
}
const callFrames1 = stackTrace1.callFrames;
const callFrames2 = stackTrace2.callFrames;
if (callFrames1.length !== callFrames2.length) {
return false;
}
for (let i = 0, n = callFrames1.length; i < n; ++i) {
if (callFrames1[i].url !== callFrames2[i].url || callFrames1[i].functionName !== callFrames2[i].functionName ||
callFrames1[i].lineNumber !== callFrames2[i].lineNumber ||
callFrames1[i].columnNumber !== callFrames2[i].columnNumber) {
return false;
}
}
return this.isEqualStackTraces(stackTrace1.parent, stackTrace2.parent);
}
}
export const MessageSourceDisplayName = new Map(([
["xml" /* XML */, 'xml'],
["javascript" /* Javascript */, 'javascript'],
["network" /* Network */, 'network'],
[FrontendMessageSource.ConsoleAPI, 'console-api'],
["storage" /* Storage */, 'storage'],
["appcache" /* Appcache */, 'appcache'],
["rendering" /* Rendering */, 'rendering'],
[FrontendMessageSource.CSS, 'css'],
["security" /* Security */, 'security'],
["deprecation" /* Deprecation */, 'deprecation'],
["worker" /* Worker */, 'worker'],
["violation" /* Violation */, 'violation'],
["intervention" /* Intervention */, 'intervention'],
["recommendation" /* Recommendation */, 'recommendation'],
["other" /* Other */, 'other'],
]));