f4fb8fc1f7
We introduce V8InspectorSession::stop API to enable safe detach from the session. In particular, after calling 'stop', the session will leave any instrumentation pause it might be in and disarm all its instrumentation breakpoints. This is useful when the session disconnect request is registered on V8 interrupt (so it is unsafe to disconnect at that point), and the execution should first get to the message loop where the disconnect can be handled safely. Bug: chromium:1354043 Change-Id: I3caab12a21b123229835e8374efadc1f4c9954c2 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4085143 Reviewed-by: Benedikt Meurer <bmeurer@chromium.org> Reviewed-by: Kim-Anh Tran <kimanh@chromium.org> Commit-Queue: Jaroslav Sevcik <jarin@chromium.org> Cr-Commit-Position: refs/heads/main@{#84753}
554 lines
19 KiB
JavaScript
554 lines
19 KiB
JavaScript
// Copyright 2016 the V8 project authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
InspectorTest = {};
|
|
InspectorTest._dumpInspectorProtocolMessages = false;
|
|
InspectorTest._commandsForLogging = new Set();
|
|
InspectorTest._sessions = new Set();
|
|
|
|
InspectorTest.log = utils.print.bind(utils);
|
|
InspectorTest.quitImmediately = utils.quit.bind(utils);
|
|
|
|
InspectorTest.logProtocolCommandCalls = function(command) {
|
|
InspectorTest._commandsForLogging.add(command);
|
|
}
|
|
|
|
InspectorTest.completeTest = function() {
|
|
var promises = [];
|
|
for (var session of InspectorTest._sessions)
|
|
promises.push(session.Protocol.Debugger.disable());
|
|
Promise.all(promises).then(() => utils.quit());
|
|
}
|
|
|
|
InspectorTest.waitForPendingTasks = function() {
|
|
var promises = [];
|
|
for (var session of InspectorTest._sessions)
|
|
promises.push(session.Protocol.Runtime.evaluate({ expression: "new Promise(r => setTimeout(r, 0))//# sourceURL=wait-for-pending-tasks.js", awaitPromise: true }));
|
|
return Promise.all(promises);
|
|
}
|
|
|
|
InspectorTest.startDumpingProtocolMessages = function() {
|
|
InspectorTest._dumpInspectorProtocolMessages = true;
|
|
}
|
|
|
|
InspectorTest.logMessage = function(originalMessage) {
|
|
const nonStableFields = new Set([
|
|
'objectId', 'scriptId', 'exceptionId', 'timestamp', 'executionContextId',
|
|
'callFrameId', 'breakpointId', 'bindRemoteObjectFunctionId',
|
|
'formatterObjectId', 'debuggerId', 'bodyGetterId', 'uniqueId'
|
|
]);
|
|
const message = JSON.parse(JSON.stringify(originalMessage, replacer.bind(null, Symbol(), nonStableFields)));
|
|
if (message.id)
|
|
message.id = '<messageId>';
|
|
|
|
InspectorTest.logObject(message);
|
|
return originalMessage;
|
|
|
|
function replacer(stableIdSymbol, nonStableFields, name, val) {
|
|
if (nonStableFields.has(name))
|
|
return `<${name}>`;
|
|
if (name === 'internalProperties') {
|
|
const stableId = val.find(prop => prop.name === '[[StableObjectId]]');
|
|
if (stableId)
|
|
stableId.value[stableIdSymbol] = true;
|
|
}
|
|
if (name === 'parentId')
|
|
return { id: '<id>' };
|
|
if (val && val[stableIdSymbol])
|
|
return '<StablectObjectId>';
|
|
return val;
|
|
}
|
|
}
|
|
|
|
InspectorTest.logObject = function(object, title) {
|
|
var lines = [];
|
|
|
|
function dumpValue(value, prefix, prefixWithName) {
|
|
if (typeof value === "object" && value !== null) {
|
|
if (value instanceof Array)
|
|
dumpItems(value, prefix, prefixWithName);
|
|
else
|
|
dumpProperties(value, prefix, prefixWithName);
|
|
} else {
|
|
lines.push(prefixWithName + String(value).replace(/\n/g, " "));
|
|
}
|
|
}
|
|
|
|
function dumpProperties(object, prefix, firstLinePrefix) {
|
|
prefix = prefix || "";
|
|
firstLinePrefix = firstLinePrefix || prefix;
|
|
lines.push(firstLinePrefix + "{");
|
|
|
|
var propertyNames = Object.keys(object);
|
|
propertyNames.sort();
|
|
for (var i = 0; i < propertyNames.length; ++i) {
|
|
var name = propertyNames[i];
|
|
if (!object.hasOwnProperty(name))
|
|
continue;
|
|
var prefixWithName = " " + prefix + name + " : ";
|
|
dumpValue(object[name], " " + prefix, prefixWithName);
|
|
}
|
|
lines.push(prefix + "}");
|
|
}
|
|
|
|
function dumpItems(object, prefix, firstLinePrefix) {
|
|
prefix = prefix || "";
|
|
firstLinePrefix = firstLinePrefix || prefix;
|
|
lines.push(firstLinePrefix + "[");
|
|
for (var i = 0; i < object.length; ++i)
|
|
dumpValue(object[i], " " + prefix, " " + prefix + "[" + i + "] : ");
|
|
lines.push(prefix + "]");
|
|
}
|
|
|
|
dumpValue(object, "", title || "");
|
|
InspectorTest.log(lines.join("\n"));
|
|
}
|
|
|
|
InspectorTest.decodeBase64 = function(base64) {
|
|
const LOOKUP = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
|
|
const paddingLength = base64.match(/=*$/)[0].length;
|
|
const bytesLength = base64.length * 0.75 - paddingLength;
|
|
|
|
let bytes = new Uint8Array(bytesLength);
|
|
|
|
for (let i = 0, p = 0; i < base64.length; i += 4, p += 3) {
|
|
let bits = 0;
|
|
for (let j = 0; j < 4; j++) {
|
|
bits <<= 6;
|
|
const c = base64[i + j];
|
|
if (c !== '=') bits |= LOOKUP.indexOf(c);
|
|
}
|
|
for (let j = p + 2; j >= p; j--) {
|
|
if (j < bytesLength) bytes[j] = bits;
|
|
bits >>= 8;
|
|
}
|
|
}
|
|
|
|
return bytes;
|
|
}
|
|
|
|
InspectorTest.trimErrorMessage = function(message) {
|
|
if (!message.error || !message.error.data)
|
|
return message;
|
|
message.error.data = message.error.data.replace(/at position \d+/,
|
|
'at <some position>');
|
|
return message;
|
|
}
|
|
|
|
InspectorTest.ContextGroup = class {
|
|
constructor() {
|
|
this.id = utils.createContextGroup();
|
|
}
|
|
|
|
waitForDebugger() {
|
|
return new Promise(resolve => {
|
|
utils.waitForDebugger(this.id, resolve);
|
|
});
|
|
}
|
|
|
|
createContext(name) {
|
|
utils.createContext(this.id, name || '');
|
|
}
|
|
|
|
schedulePauseOnNextStatement(reason, details) {
|
|
utils.schedulePauseOnNextStatement(this.id, reason, details);
|
|
}
|
|
|
|
cancelPauseOnNextStatement() {
|
|
utils.cancelPauseOnNextStatement(this.id);
|
|
}
|
|
|
|
stop() {
|
|
utils.stop(this.id);
|
|
}
|
|
|
|
addScript(string, lineOffset, columnOffset, url) {
|
|
utils.compileAndRunWithOrigin(this.id, string, url || '', lineOffset || 0, columnOffset || 0, false);
|
|
}
|
|
|
|
addInlineScript(string, url) {
|
|
const match = (new Error().stack).split('\n')[2].match(/([0-9]+):([0-9]+)/);
|
|
this.addScript(
|
|
string, match[1] * 1, match[1] * 1 + '.addInlineScript('.length, url);
|
|
}
|
|
|
|
addModule(string, url, lineOffset, columnOffset) {
|
|
utils.compileAndRunWithOrigin(this.id, string, url, lineOffset || 0, columnOffset || 0, true);
|
|
}
|
|
|
|
loadScript(fileName) {
|
|
this.addScript(utils.read(fileName));
|
|
}
|
|
|
|
connect() {
|
|
return new InspectorTest.Session(this);
|
|
}
|
|
|
|
reset() {
|
|
utils.resetContextGroup(this.id);
|
|
}
|
|
|
|
setupInjectedScriptEnvironment(session) {
|
|
let scriptSource = '';
|
|
let getters = ["length","internalConstructorName","subtype","getProperty",
|
|
"objectHasOwnProperty","nullifyPrototype","primitiveTypes",
|
|
"closureTypes","prototype","all","RemoteObject","bind",
|
|
"PropertyDescriptor","object","get","set","value","configurable",
|
|
"enumerable","symbol","getPrototypeOf","nativeAccessorDescriptor",
|
|
"isBuiltin","hasGetter","hasSetter","getOwnPropertyDescriptor",
|
|
"description","isOwn","name",
|
|
"typedArrayProperties","keys","getOwnPropertyNames",
|
|
"getOwnPropertySymbols","isPrimitiveValue","com","toLowerCase",
|
|
"ELEMENT","trim","replace","DOCUMENT","size","byteLength","toString",
|
|
"stack","substr","message","indexOf","key","type","unserializableValue",
|
|
"objectId","className","preview","proxyTargetValue","customPreview",
|
|
"CustomPreview","resolve","then","console","error","header","hasBody",
|
|
"stringify","ObjectPreview","ObjectPreviewType","properties",
|
|
"ObjectPreviewSubtype","getInternalProperties","wasThrown","indexes",
|
|
"overflow","valuePreview","entries"];
|
|
scriptSource += `(function installSettersAndGetters() {
|
|
let defineProperty = Object.defineProperty;
|
|
let ObjectPrototype = Object.prototype;
|
|
let ArrayPrototype = Array.prototype;
|
|
defineProperty(ArrayPrototype, 0, {
|
|
set() { debugger; throw 42; }, get() { debugger; throw 42; },
|
|
__proto__: null
|
|
});`,
|
|
scriptSource += getters.map(getter => `
|
|
defineProperty(ObjectPrototype, '${getter}', {
|
|
set() { debugger; throw 42; }, get() { debugger; throw 42; },
|
|
__proto__: null
|
|
});
|
|
`).join('\n') + '})();';
|
|
this.addScript(scriptSource);
|
|
|
|
if (session) {
|
|
InspectorTest.log('WARNING: setupInjectedScriptEnvironment with debug flag for debugging only and should not be landed.');
|
|
session.setupScriptMap();
|
|
session.Protocol.Debugger.enable();
|
|
session.Protocol.Debugger.onPaused(message => {
|
|
let callFrames = message.params.callFrames;
|
|
session.logSourceLocations(callFrames.map(frame => frame.location));
|
|
})
|
|
}
|
|
}
|
|
};
|
|
|
|
InspectorTest.Session = class {
|
|
constructor(contextGroup) {
|
|
this.contextGroup = contextGroup;
|
|
this._dispatchTable = new Map();
|
|
this._eventHandlers = new Map();
|
|
this._requestId = 0;
|
|
this.Protocol = this._setupProtocol();
|
|
InspectorTest._sessions.add(this);
|
|
this.id = utils.connectSession(contextGroup.id, '', this._dispatchMessage.bind(this));
|
|
}
|
|
|
|
disconnect() {
|
|
InspectorTest._sessions.delete(this);
|
|
utils.disconnectSession(this.id);
|
|
}
|
|
|
|
reconnect() {
|
|
var state = utils.disconnectSession(this.id);
|
|
this.id = utils.connectSession(this.contextGroup.id, state, this._dispatchMessage.bind(this));
|
|
}
|
|
|
|
async addInspectedObject(serializable) {
|
|
return this.Protocol.Runtime.evaluate({expression: `inspector.addInspectedObject(${this.id}, ${JSON.stringify(serializable)})`});
|
|
}
|
|
|
|
sendRawCommand(requestId, command, handler) {
|
|
if (InspectorTest._dumpInspectorProtocolMessages)
|
|
utils.print("frontend: " + command);
|
|
this._dispatchTable.set(requestId, handler);
|
|
utils.sendMessageToBackend(this.id, command);
|
|
}
|
|
|
|
setupScriptMap() {
|
|
if (this._scriptMap)
|
|
return;
|
|
this._scriptMap = new Map();
|
|
}
|
|
|
|
getCallFrameUrl(frame) {
|
|
const {scriptId} = frame.location ? frame.location : frame;
|
|
return (this._scriptMap.get(scriptId) ?? frame).url;
|
|
}
|
|
|
|
logCallFrames(callFrames) {
|
|
for (var frame of callFrames) {
|
|
var functionName = frame.functionName || '(anonymous)';
|
|
var url = this.getCallFrameUrl(frame);
|
|
var lineNumber = frame.location ? frame.location.lineNumber : frame.lineNumber;
|
|
var columnNumber = frame.location ? frame.location.columnNumber : frame.columnNumber;
|
|
InspectorTest.log(`${functionName} (${url}:${lineNumber}:${columnNumber})`);
|
|
}
|
|
}
|
|
|
|
async getScriptWithSource(scriptId, forceSourceRequest) {
|
|
var script = this._scriptMap.get(scriptId);
|
|
if (forceSourceRequest || !(script.scriptSource || script.bytecode)) {
|
|
var message = await this.Protocol.Debugger.getScriptSource({ scriptId });
|
|
script.scriptSource = message.result.scriptSource;
|
|
if (message.result.bytecode) {
|
|
script.bytecode = InspectorTest.decodeBase64(message.result.bytecode);
|
|
}
|
|
}
|
|
return script;
|
|
}
|
|
|
|
async logSourceLocation(location, forceSourceRequest) {
|
|
var scriptId = location.scriptId;
|
|
if (!this._scriptMap || !this._scriptMap.has(scriptId)) {
|
|
InspectorTest.log("setupScriptMap should be called before Protocol.Debugger.enable.");
|
|
InspectorTest.completeTest();
|
|
}
|
|
var script = await this.getScriptWithSource(scriptId, forceSourceRequest);
|
|
|
|
if (script.bytecode) {
|
|
if (location.lineNumber != 0) {
|
|
InspectorTest.log('Unexpected wasm line number: ' + location.lineNumber);
|
|
}
|
|
let wasm_opcode = script.bytecode[location.columnNumber];
|
|
let opcode_str = wasm_opcode.toString(16);
|
|
if (opcode_str.length % 2) opcode_str = `0${opcode_str}`;
|
|
if (InspectorTest.getWasmOpcodeName) {
|
|
opcode_str += ` (${InspectorTest.getWasmOpcodeName(wasm_opcode)})`;
|
|
}
|
|
InspectorTest.log(`Script ${script.url} byte offset ${
|
|
location.columnNumber}: Wasm opcode 0x${opcode_str}`);
|
|
} else {
|
|
var lines = script.scriptSource.split('\n');
|
|
var line = lines[location.lineNumber];
|
|
line = line.slice(0, location.columnNumber) + '#' + (line.slice(location.columnNumber) || '');
|
|
lines[location.lineNumber] = line;
|
|
lines = lines.filter(line => line.indexOf('//# sourceURL=') === -1);
|
|
InspectorTest.log(lines.slice(Math.max(location.lineNumber - 1, 0), location.lineNumber + 2).join('\n'));
|
|
InspectorTest.log('');
|
|
}
|
|
}
|
|
|
|
logSourceLocations(locations) {
|
|
if (locations.length == 0) return Promise.resolve();
|
|
return this.logSourceLocation(locations[0]).then(() => this.logSourceLocations(locations.splice(1)));
|
|
}
|
|
|
|
async logBreakLocations(inputLocations) {
|
|
let locations = inputLocations.slice();
|
|
let scriptId = locations[0].scriptId;
|
|
let script = await this.getScriptWithSource(scriptId);
|
|
let lines = script.scriptSource.split('\n');
|
|
locations = locations.sort((loc1, loc2) => {
|
|
if (loc2.lineNumber !== loc1.lineNumber) return loc2.lineNumber - loc1.lineNumber;
|
|
return loc2.columnNumber - loc1.columnNumber;
|
|
});
|
|
for (let location of locations) {
|
|
let line = lines[location.lineNumber];
|
|
line = line.slice(0, location.columnNumber) + locationMark(location.type) + line.slice(location.columnNumber);
|
|
lines[location.lineNumber] = line;
|
|
}
|
|
lines = lines.filter(line => line.indexOf('//# sourceURL=') === -1);
|
|
InspectorTest.log(lines.join('\n') + '\n');
|
|
return inputLocations;
|
|
|
|
function locationMark(type) {
|
|
if (type === 'return') return '|R|';
|
|
if (type === 'call') return '|C|';
|
|
if (type === 'debuggerStatement') return '|D|';
|
|
return '|_|';
|
|
}
|
|
}
|
|
|
|
async logTypeProfile(typeProfile, source) {
|
|
let entries = typeProfile.entries;
|
|
|
|
// Sort in reverse order so we can replace entries without invalidating
|
|
// the other offsets.
|
|
entries = entries.sort((a, b) => b.offset - a.offset);
|
|
|
|
for (let entry of entries) {
|
|
source = source.slice(0, entry.offset) + typeAnnotation(entry.types) +
|
|
source.slice(entry.offset);
|
|
}
|
|
InspectorTest.log(source);
|
|
return typeProfile;
|
|
|
|
function typeAnnotation(types) {
|
|
return `/*${types.map(t => t.name).join(', ')}*/`;
|
|
}
|
|
}
|
|
|
|
logAsyncStackTrace(asyncStackTrace) {
|
|
while (asyncStackTrace) {
|
|
InspectorTest.log(`-- ${asyncStackTrace.description || '<empty>'} --`);
|
|
this.logCallFrames(asyncStackTrace.callFrames);
|
|
if (asyncStackTrace.parentId) InspectorTest.log(' <external stack>');
|
|
asyncStackTrace = asyncStackTrace.parent;
|
|
}
|
|
}
|
|
|
|
_sendCommandPromise(method, params) {
|
|
if (typeof params !== 'object')
|
|
utils.print(`WARNING: non-object params passed to invocation of method ${method}`);
|
|
if (InspectorTest._commandsForLogging.has(method))
|
|
utils.print(method + ' called');
|
|
var requestId = ++this._requestId;
|
|
var messageObject = { "id": requestId, "method": method, "params": params };
|
|
return new Promise(fulfill => this.sendRawCommand(requestId, JSON.stringify(messageObject), fulfill));
|
|
}
|
|
|
|
_setupProtocol() {
|
|
return new Proxy({}, { get: (target, agentName, receiver) => new Proxy({}, {
|
|
get: (target, methodName, receiver) => {
|
|
const eventPattern = /^on(ce)?([A-Z][A-Za-z0-9]+)/;
|
|
var match = eventPattern.exec(methodName);
|
|
if (!match)
|
|
return args => this._sendCommandPromise(`${agentName}.${methodName}`, args || {});
|
|
var eventName = match[2];
|
|
eventName = eventName.charAt(0).toLowerCase() + eventName.slice(1);
|
|
if (match[1])
|
|
return numOfEvents => this._waitForEventPromise(
|
|
`${agentName}.${eventName}`, numOfEvents || 1);
|
|
return listener => this._eventHandlers.set(`${agentName}.${eventName}`, listener);
|
|
}
|
|
})});
|
|
}
|
|
|
|
_dispatchMessage(messageString) {
|
|
var messageObject = JSON.parse(messageString);
|
|
if (InspectorTest._dumpInspectorProtocolMessages)
|
|
utils.print("backend: " + JSON.stringify(messageObject));
|
|
const kMethodNotFound = -32601;
|
|
if (messageObject.error && messageObject.error.code === kMethodNotFound) {
|
|
InspectorTest.log(`Error: Called non-existent method. ${
|
|
messageObject.error.message} code: ${messageObject.error.code}`);
|
|
InspectorTest.completeTest();
|
|
}
|
|
try {
|
|
var messageId = messageObject["id"];
|
|
if (typeof messageId === "number") {
|
|
var handler = this._dispatchTable.get(messageId);
|
|
if (handler) {
|
|
handler(messageObject);
|
|
this._dispatchTable.delete(messageId);
|
|
}
|
|
} else {
|
|
var eventName = messageObject["method"];
|
|
var eventHandler = this._eventHandlers.get(eventName);
|
|
if (this._scriptMap && eventName === "Debugger.scriptParsed")
|
|
this._scriptMap.set(messageObject.params.scriptId, JSON.parse(JSON.stringify(messageObject.params)));
|
|
if (eventName === "Debugger.scriptParsed" && messageObject.params.url === "wait-for-pending-tasks.js")
|
|
return;
|
|
if (eventHandler)
|
|
eventHandler(messageObject);
|
|
}
|
|
} catch (e) {
|
|
InspectorTest.log("Exception when dispatching message: " + e + "\n" + e.stack + "\n message = " + JSON.stringify(messageObject, null, 2));
|
|
InspectorTest.completeTest();
|
|
}
|
|
};
|
|
|
|
_waitForEventPromise(eventName, numOfEvents) {
|
|
let events = [];
|
|
return new Promise(fulfill => {
|
|
this._eventHandlers.set(eventName, result => {
|
|
--numOfEvents;
|
|
events.push(result);
|
|
if (numOfEvents === 0) {
|
|
delete this._eventHandlers.delete(eventName);
|
|
fulfill(events.length > 1 ? events : events[0]);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
};
|
|
|
|
InspectorTest.runTestSuite = function(testSuite) {
|
|
function nextTest() {
|
|
if (!testSuite.length) {
|
|
InspectorTest.completeTest();
|
|
return;
|
|
}
|
|
var fun = testSuite.shift();
|
|
InspectorTest.log("\nRunning test: " + fun.name);
|
|
fun(nextTest);
|
|
}
|
|
nextTest();
|
|
}
|
|
|
|
InspectorTest.runAsyncTestSuite = async function(testSuite) {
|
|
const selected = testSuite.filter(test => test.name.startsWith('f_'));
|
|
if (selected.length)
|
|
testSuite = selected;
|
|
for (var test of testSuite) {
|
|
InspectorTest.log("\nRunning test: " + test.name);
|
|
try {
|
|
await test();
|
|
} catch (e) {
|
|
utils.print(e.stack || "Caught error without stack trace!");
|
|
}
|
|
}
|
|
InspectorTest.completeTest();
|
|
}
|
|
|
|
InspectorTest.start = function(description) {
|
|
try {
|
|
InspectorTest.log(description);
|
|
var contextGroup = new InspectorTest.ContextGroup();
|
|
var session = contextGroup.connect();
|
|
return { session: session, contextGroup: contextGroup, Protocol: session.Protocol };
|
|
} catch (e) {
|
|
utils.print(e.stack);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Two helper functions for the tests in `debugger/restart-frame/*`.
|
|
*/
|
|
|
|
InspectorTest.evaluateAndWaitForPause = async (expression) => {
|
|
const pausedPromise = Protocol.Debugger.oncePaused();
|
|
const evaluatePromise = Protocol.Runtime.evaluate({ expression });
|
|
|
|
const { params: { callFrames } } = await pausedPromise;
|
|
InspectorTest.log('Paused at (after evaluation):');
|
|
await session.logSourceLocation(callFrames[0].location);
|
|
|
|
// Ignore the last frame, it's always an anonymous empty frame for the
|
|
// Runtime#evaluate call.
|
|
InspectorTest.log('Pause stack:');
|
|
for (const frame of callFrames.slice(0, -1)) {
|
|
InspectorTest.log(` ${frame.functionName}:${frame.location.lineNumber} (canBeRestarted = ${frame.canBeRestarted ?? false})`);
|
|
}
|
|
InspectorTest.log('');
|
|
|
|
return { callFrames, evaluatePromise };
|
|
};
|
|
|
|
// TODO(crbug.com/1303521): Remove `quitOnFailure` once no longer needed.
|
|
InspectorTest.restartFrameAndWaitForPause = async (callFrames, index, quitOnFailure = true) => {
|
|
const pausedPromise = Protocol.Debugger.oncePaused();
|
|
const frame = callFrames[index];
|
|
|
|
InspectorTest.log(`Restarting function "${frame.functionName}" ...`);
|
|
const response = await Protocol.Debugger.restartFrame({ callFrameId: frame.callFrameId, mode: 'StepInto' });
|
|
if (response.error) {
|
|
InspectorTest.log(`Failed to restart function "${frame.functionName}":`);
|
|
InspectorTest.logMessage(response.error);
|
|
if (quitOnFailure) {
|
|
InspectorTest.completeTest();
|
|
}
|
|
return;
|
|
}
|
|
|
|
const { params: { callFrames: pausedCallFrames } } = await pausedPromise;
|
|
InspectorTest.log('Paused at (after restart):');
|
|
await session.logSourceLocation(pausedCallFrames[0].location);
|
|
|
|
return callFrames;
|
|
};
|