[inspector] Add Runtime#getExceptionDetails CDP method
CDP has a "ExceptionDetails" structure that is attached to various CDP commands, e.g. "Runtime#exceptionThrown" or "Runtime#evaluate". The stack trace in the "ExceptionDetails" structure is used in various places in DevTools. The information in the "ExceptionDetails" structure is extracted from a v8::Message object. Message objects are normally created at the exception throw site and may augment the error with manually inspecting the stack (both to capture a fresh stack trace in some cases, as well as to calculate location info). The problem is that in some cases we want to get an "ExceptionDetails" structure after the fact, e.g. when logging a JS "Error" object in a catch block. To help in this case, this CL introduces a new CDP method "Runtime#getExceptionDetails" that behaves exactly as advertised: It provides a populated "ExceptionDetails" structure from a JS Error object. R=bmeurer@chromium.org Doc: https://bit.ly/runtime-get-exception-details Bug: chromium:1278650 Change-Id: I084be10c1d852d3b7cac8d88e7f820e867be4722 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3337258 Commit-Queue: Simon Zünd <szuend@chromium.org> Reviewed-by: Yang Guo <yangguo@chromium.org> Cr-Commit-Position: refs/heads/main@{#78676}
This commit is contained in:
parent
1511a19d5a
commit
1f53cbf197
@ -1552,6 +1552,18 @@ domain Runtime
|
||||
parameters
|
||||
string name
|
||||
|
||||
# This method tries to lookup and populate exception details for a
|
||||
# JavaScript Error object.
|
||||
# Note that the stackTrace portion of the resulting exceptionDetails will
|
||||
# only be populated if the Runtime domain was enabled at the time when the
|
||||
# Error was thrown.
|
||||
experimental command getExceptionDetails
|
||||
parameters
|
||||
# The error object for which to resolve the exception details.
|
||||
RemoteObjectId errorObjectId
|
||||
returns
|
||||
optional ExceptionDetails exceptionDetails
|
||||
|
||||
# Notification is issued every time when binding is called.
|
||||
experimental event bindingCalled
|
||||
parameters
|
||||
|
@ -809,6 +809,34 @@ Response V8RuntimeAgentImpl::removeBinding(const String16& name) {
|
||||
return Response::Success();
|
||||
}
|
||||
|
||||
Response V8RuntimeAgentImpl::getExceptionDetails(
|
||||
const String16& errorObjectId,
|
||||
Maybe<protocol::Runtime::ExceptionDetails>* out_exceptionDetails) {
|
||||
InjectedScript::ObjectScope scope(m_session, errorObjectId);
|
||||
Response response = scope.initialize();
|
||||
if (!response.IsSuccess()) return response;
|
||||
|
||||
const v8::Local<v8::Value> error = scope.object();
|
||||
if (!error->IsNativeError())
|
||||
return Response::InvalidParams("errorObjectId is not a JS error object");
|
||||
|
||||
const v8::Local<v8::Message> message =
|
||||
v8::debug::CreateMessageFromException(m_inspector->isolate(), error);
|
||||
|
||||
response = scope.injectedScript()->createExceptionDetails(
|
||||
message, error, scope.objectGroupName(), out_exceptionDetails);
|
||||
if (!response.IsSuccess()) return response;
|
||||
|
||||
CHECK(out_exceptionDetails->isJust());
|
||||
|
||||
// When an exception object is present, `createExceptionDetails` assumes
|
||||
// the exception is uncaught and will overwrite the text field to "Uncaught".
|
||||
// Lets use the normal message text instead.
|
||||
out_exceptionDetails->fromJust()->setText(
|
||||
toProtocolString(m_inspector->isolate(), message->Get()));
|
||||
return Response::Success();
|
||||
}
|
||||
|
||||
void V8RuntimeAgentImpl::bindingCalled(const String16& name,
|
||||
const String16& payload,
|
||||
int executionContextId) {
|
||||
|
@ -128,6 +128,9 @@ class V8RuntimeAgentImpl : public protocol::Runtime::Backend {
|
||||
Maybe<String16> executionContextName) override;
|
||||
Response removeBinding(const String16& name) override;
|
||||
void addBindings(InspectedContext* context);
|
||||
Response getExceptionDetails(const String16& errorObjectId,
|
||||
Maybe<protocol::Runtime::ExceptionDetails>*
|
||||
out_exceptionDetails) override;
|
||||
|
||||
void reset();
|
||||
void reportExecutionContextCreated(InspectedContext*);
|
||||
|
70
test/inspector/runtime/get-exception-details-expected.txt
Normal file
70
test/inspector/runtime/get-exception-details-expected.txt
Normal file
@ -0,0 +1,70 @@
|
||||
Tests that Runtime.getExceptionDetails works
|
||||
|
||||
Running test: itShouldReturnExceptionDetailsForJSErrorObjects
|
||||
{
|
||||
id : <messageId>
|
||||
result : {
|
||||
exceptionDetails : {
|
||||
columnNumber : 9
|
||||
exception : {
|
||||
className : Error
|
||||
description : Error: error 1 at foo (<anonymous>:3:10) at <anonymous>:5:1
|
||||
objectId : <objectId>
|
||||
subtype : error
|
||||
type : object
|
||||
}
|
||||
exceptionId : <exceptionId>
|
||||
lineNumber : 2
|
||||
scriptId : <scriptId>
|
||||
stackTrace : {
|
||||
callFrames : [
|
||||
[0] : {
|
||||
columnNumber : 9
|
||||
functionName : foo
|
||||
lineNumber : 2
|
||||
scriptId : <scriptId>
|
||||
url :
|
||||
}
|
||||
[1] : {
|
||||
columnNumber : 0
|
||||
functionName :
|
||||
lineNumber : 4
|
||||
scriptId : <scriptId>
|
||||
url :
|
||||
}
|
||||
]
|
||||
}
|
||||
text : Error: error 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Running test: itShouldReturnIncompleteDetailsForJSErrorWithNoStack
|
||||
{
|
||||
id : <messageId>
|
||||
result : {
|
||||
exceptionDetails : {
|
||||
columnNumber : -1
|
||||
exception : {
|
||||
className : Error
|
||||
description : Error: error 1 at foo (<anonymous>:3:10) at <anonymous>:5:1
|
||||
objectId : <objectId>
|
||||
subtype : error
|
||||
type : object
|
||||
}
|
||||
exceptionId : <exceptionId>
|
||||
lineNumber : -1
|
||||
scriptId : <scriptId>
|
||||
text : Error: error 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Running test: itShouldReportAnErrorForNonJSErrorObjects
|
||||
{
|
||||
error : {
|
||||
code : -32602
|
||||
message : errorObjectId is not a JS error object
|
||||
}
|
||||
id : <messageId>
|
||||
}
|
35
test/inspector/runtime/get-exception-details.js
Normal file
35
test/inspector/runtime/get-exception-details.js
Normal file
@ -0,0 +1,35 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
const {session, contextGroup, Protocol} =
|
||||
InspectorTest.start('Tests that Runtime.getExceptionDetails works');
|
||||
|
||||
const expression = `
|
||||
function foo() {
|
||||
return new Error("error 1");
|
||||
}
|
||||
foo();
|
||||
`;
|
||||
|
||||
InspectorTest.runAsyncTestSuite([
|
||||
async function itShouldReturnExceptionDetailsForJSErrorObjects() {
|
||||
await Protocol.Runtime.enable(); // Enable detailed stacktrace capturing.
|
||||
const {result} = await Protocol.Runtime.evaluate({expression});
|
||||
InspectorTest.logMessage(await Protocol.Runtime.getExceptionDetails(
|
||||
{errorObjectId: result.result.objectId}));
|
||||
},
|
||||
|
||||
async function itShouldReturnIncompleteDetailsForJSErrorWithNoStack() {
|
||||
await Protocol.Runtime.disable(); // Disable detailed stacktrace capturing.
|
||||
const {result} = await Protocol.Runtime.evaluate({expression});
|
||||
InspectorTest.logMessage(await Protocol.Runtime.getExceptionDetails(
|
||||
{errorObjectId: result.result.objectId}));
|
||||
},
|
||||
|
||||
async function itShouldReportAnErrorForNonJSErrorObjects() {
|
||||
const {result} = await Protocol.Runtime.evaluate({expression: '() =>({})'});
|
||||
InspectorTest.logMessage(await Protocol.Runtime.getExceptionDetails(
|
||||
{errorObjectId: result.result.objectId}));
|
||||
}
|
||||
]);
|
Loading…
Reference in New Issue
Block a user