blueslip: Show popups for thrown non-Error values in development.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg 2024-06-24 13:12:13 -07:00 committed by Tim Abbott
parent 18137709a1
commit 148af2a301
3 changed files with 40 additions and 27 deletions

View File

@ -111,7 +111,7 @@ export function error(msg: string, more_info?: object | undefined, original_erro
if (DEVELOPMENT) { if (DEVELOPMENT) {
$(window).on("error", (event: JQuery.TriggeredEvent) => { $(window).on("error", (event: JQuery.TriggeredEvent) => {
const {originalEvent} = event; const {originalEvent} = event;
if (originalEvent instanceof ErrorEvent && originalEvent.error instanceof Error) { if (originalEvent instanceof ErrorEvent) {
void display_stacktrace(originalEvent.error); void display_stacktrace(originalEvent.error);
} }
}); });

View File

@ -109,41 +109,54 @@ async function get_context(location: StackFrame): Promise<NumberedLine[] | undef
})); }));
} }
export async function display_stacktrace(ex: Error): Promise<void> { export async function display_stacktrace(ex: unknown): Promise<void> {
const errors = []; const errors = [];
while (true) { do {
const stackframes: CleanStackFrame[] = await Promise.all( if (!(ex instanceof Error)) {
ErrorStackParser.parse(ex).map(async (location: StackFrame) => { const prototype: unknown = Object.getPrototypeOf(ex);
try { errors.push({
location = await stack_trace_gps.getMappedLocation(location); name:
} catch { typeof prototype === "object" &&
// Use unmapped location prototype !== null &&
} "constructor" in prototype
return { ? `thrown ${prototype.constructor.name}`
full_path: location.getFileName(), : "thrown",
show_path: clean_path(location.getFileName()), message: String(ex),
line_number: location.getLineNumber(), stackframes: [],
function_name: clean_function_name(location.getFunctionName()), });
context: await get_context(location), break;
}; }
}), const stackframes: CleanStackFrame[] =
); ex instanceof Error
? await Promise.all(
ErrorStackParser.parse(ex).map(async (location: StackFrame) => {
try {
location = await stack_trace_gps.getMappedLocation(location);
} catch {
// Use unmapped location
}
return {
full_path: location.getFileName(),
show_path: clean_path(location.getFileName()),
line_number: location.getLineNumber(),
function_name: clean_function_name(location.getFunctionName()),
context: await get_context(location),
};
}),
)
: [];
let more_info: string | undefined; let more_info: string | undefined;
if (ex instanceof BlueslipError) { if (ex instanceof BlueslipError) {
more_info = JSON.stringify(ex.more_info, null, 4); more_info = JSON.stringify(ex.more_info, null, 4);
} }
errors.push({ errors.push({
name: ex.name,
message: exception_msg(ex), message: exception_msg(ex),
more_info, more_info,
stackframes, stackframes,
}); });
ex = ex.cause;
if (ex.cause !== undefined && ex.cause instanceof Error) { } while (ex !== undefined);
ex = ex.cause;
} else {
break;
}
}
const $alert = $("<div>").addClass("stacktrace").html(render_blueslip_stacktrace({errors})); const $alert = $("<div>").addClass("stacktrace").html(render_blueslip_stacktrace({errors}));
$(".alert-box").append($alert); $(".alert-box").append($alert);

View File

@ -3,7 +3,7 @@
<div class="warning-symbol"> <div class="warning-symbol">
<i class="fa fa-exclamation-triangle"></i> <i class="fa fa-exclamation-triangle"></i>
</div> </div>
<div class="message"><strong>Error:</strong> {{ message }}</div> <div class="message">{{#unless @first}}caused by {{/unless}}<strong>{{name}}:</strong> {{ message }}</div>
{{#if @first}} {{#if @first}}
<div class="exit"></div> <div class="exit"></div>
{{/if}} {{/if}}