Skip to content

Commit 936ed82

Browse files
szuenddevtools-frontend-scoped@luci-project-accounts.iam.gserviceaccount.com
authored andcommitted
[bindings] Create SymbolizedError from string RemoteObjects with details
We support parsing not only for RemoteObjects of subtype 'error' but also RemoteObjects of type 'string'. This is because it is very common to `console.log(e.stack)` and we want to source-map and stringify that. Moreover, for uncaught errors, we already have the exception details so we can pass those in as well instead of re-fetching them from the backend. R=kimanh@chromium.org Bug: 485140575 Change-Id: Ie001d20cba01026b0741ddcb621159361836d17e Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/7784557 Reviewed-by: Kim-Anh Tran <kimanh@chromium.org> Commit-Queue: Simon Zünd <szuend@chromium.org>
1 parent 1b18f18 commit 936ed82

2 files changed

Lines changed: 87 additions & 10 deletions

File tree

front_end/models/bindings/DebuggerWorkspaceBinding.ts

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -215,28 +215,41 @@ export class DebuggerWorkspaceBinding implements SDK.TargetManager.SDKModelObser
215215
return await stackTracePromise;
216216
}
217217

218-
async createSymbolizedError(remoteObject: SDK.RemoteObject.RemoteObject): Promise<SymbolizedError|null> {
219-
if (remoteObject.subtype !== 'error') {
218+
async createSymbolizedError(
219+
remoteObject: SDK.RemoteObject.RemoteObject,
220+
exceptionDetails?: Protocol.Runtime.ExceptionDetails): Promise<SymbolizedError|null> {
221+
let errorStack = '';
222+
let causeRemoteObject: SDK.RemoteObject.RemoteObject|undefined;
223+
let fetchedExceptionDetails = exceptionDetails;
224+
225+
if (remoteObject.subtype === 'error') {
226+
const remoteError = SDK.RemoteObject.RemoteError.objectAsError(remoteObject);
227+
errorStack = remoteError.errorStack;
228+
229+
const [details, causeRemote] = await Promise.all([
230+
exceptionDetails ? Promise.resolve(exceptionDetails) : remoteError.exceptionDetails(),
231+
remoteError.cause(),
232+
]);
233+
fetchedExceptionDetails = details;
234+
causeRemoteObject = causeRemote;
235+
236+
} else if (remoteObject.type === 'string') {
237+
errorStack = remoteObject.description || '';
238+
} else {
220239
return null;
221240
}
222241

223-
const remoteError = SDK.RemoteObject.RemoteError.objectAsError(remoteObject);
224-
const [exceptionDetails, causeRemoteObject] = await Promise.all([
225-
remoteError.exceptionDetails(),
226-
remoteError.cause(),
227-
]);
228-
229242
const [stackTrace, cause] = await Promise.all([
230243
this.createStackTraceFromErrorStackLikeString(
231-
remoteObject.runtimeModel().target(), remoteError.errorStack, exceptionDetails),
244+
remoteObject.runtimeModel().target(), errorStack, fetchedExceptionDetails),
232245
causeRemoteObject ? this.createSymbolizedError(causeRemoteObject) : Promise.resolve(null),
233246
]);
234247

235248
if (!stackTrace) {
236249
return null;
237250
}
238251

239-
const message = StackTraceImpl.DetailedErrorStackParser.parseMessage(remoteError.errorStack);
252+
const message = StackTraceImpl.DetailedErrorStackParser.parseMessage(errorStack);
240253
return new SymbolizedError(message, stackTrace, cause);
241254
}
242255

front_end/models/bindings/SymbolizedError.test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,70 @@ describe('SymbolizedError', () => {
9898
assert.isNull(result);
9999
});
100100

101+
it('can create a SymbolizedError from a string RemoteObject', async () => {
102+
const target = universe.createTarget({});
103+
const runtimeModel = target.model(SDK.RuntimeModel.RuntimeModel);
104+
assert.exists(runtimeModel);
105+
106+
const stringRemoteObject = {
107+
type: 'string',
108+
description: 'Error: string error\n at http://example.com/script.js:1:1',
109+
runtimeModel: () => runtimeModel,
110+
} as unknown as SDK.RemoteObject.RemoteObject;
111+
112+
const symbolizedError = await universe.debuggerWorkspaceBinding.createSymbolizedError(stringRemoteObject);
113+
114+
assert.exists(symbolizedError);
115+
assert.strictEqual(symbolizedError.message, 'Error: string error');
116+
assert.strictEqual(symbolizedError.stackTrace.syncFragment.frames[0].url, 'http://example.com/script.js');
117+
assert.isNull(symbolizedError.cause);
118+
});
119+
120+
it('returns null for a string RemoteObject if the stack trace cannot be parsed', async () => {
121+
const target = universe.createTarget({});
122+
const runtimeModel = target.model(SDK.RuntimeModel.RuntimeModel);
123+
assert.exists(runtimeModel);
124+
125+
const stringRemoteObject = {
126+
type: 'string',
127+
description: 'Error: string error\n at http://example.com/script.js:1:1\ninvalid line',
128+
runtimeModel: () => runtimeModel,
129+
} as unknown as SDK.RemoteObject.RemoteObject;
130+
131+
const result = await universe.debuggerWorkspaceBinding.createSymbolizedError(stringRemoteObject);
132+
assert.isNull(result);
133+
});
134+
135+
it('uses the provided exceptionDetails preferentially', async () => {
136+
const target = universe.createTarget({});
137+
const runtimeModel = target.model(SDK.RuntimeModel.RuntimeModel);
138+
assert.exists(runtimeModel);
139+
140+
const errorRemoteObject = {
141+
subtype: 'error',
142+
description: 'Error: error\n at http://example.com/script.js:1:1',
143+
runtimeModel: () => runtimeModel,
144+
objectId: '1' as Protocol.Runtime.RemoteObjectId,
145+
getAllProperties: async () => ({properties: [], internalProperties: []}),
146+
} as unknown as SDK.RemoteObject.RemoteObject;
147+
148+
const exceptionDetails = {
149+
exceptionId: 1,
150+
text: 'Uncaught',
151+
lineNumber: 0,
152+
columnNumber: 0,
153+
} as Protocol.Runtime.ExceptionDetails;
154+
155+
const invokeGetExceptionDetailsSpy = sinon.spy(target.runtimeAgent(), 'invoke_getExceptionDetails');
156+
157+
const symbolizedError =
158+
await universe.debuggerWorkspaceBinding.createSymbolizedError(errorRemoteObject, exceptionDetails);
159+
160+
assert.exists(symbolizedError);
161+
assert.strictEqual(symbolizedError.message, 'Error: error');
162+
sinon.assert.notCalled(invokeGetExceptionDetailsSpy);
163+
});
164+
101165
it('emits UPDATED when stackTrace or cause updates', async () => {
102166
const symbolizedError = await createSymbolizedErrorWithCause();
103167
assert.exists(symbolizedError);

0 commit comments

Comments
 (0)