Merge "ui: Fix Wasm error reporting" into main
diff --git a/ui/src/base/logging.ts b/ui/src/base/logging.ts
index 04e0bc7..05b288d 100644
--- a/ui/src/base/logging.ts
+++ b/ui/src/base/logging.ts
@@ -60,16 +60,36 @@
 
   if (err instanceof ErrorEvent) {
     errType = 'ERROR';
-    errMsg = `${err.error}`;
-    errorObj = err.error;
+    // In nominal cases the error is set in err.error{message,stack} and
+    // a toString() of the error object returns a meaningful one-line
+    // description. However, in the case of wasm errors, emscripten seems to
+    // wrap the error in an unusual way: err.error is null but err.message
+    // contains the whole one-line + stack trace.
+    if (err.error === null || err.error === undefined) {
+      // Wasm case.
+      const errLines = `${err.message}`.split('\n');
+      errMsg = errLines[0];
+      errorObj = {stack: errLines.slice(1).join('\n')};
+    } else {
+      // Standard JS case.
+      errMsg = `${err.error}`;
+      errorObj = err.error;
+    }
   } else if (err instanceof PromiseRejectionEvent) {
     errType = 'PROMISE_REJ';
-    errMsg = `PromiseRejection: ${err.reason}`;
+    errMsg = `${err.reason}`;
     errorObj = err.reason;
   } else {
     errType = 'OTHER';
-    errMsg = `Err: ${err}`;
+    errMsg = `${err}`;
   }
+
+  // Remove useless "Uncaught Error:" or "Error:" prefixes which just create
+  // noise in the bug tracker without adding any meaningful value.
+  errMsg = errMsg.replace(/^Uncaught Error:/, '');
+  errMsg = errMsg.replace(/^Error:/, '');
+  errMsg = errMsg.trim();
+
   if (errorObj !== undefined && errorObj !== null) {
     const maybeStack = (errorObj as {stack?: string}).stack;
     let errStack = maybeStack !== undefined ? `${maybeStack}` : '';
@@ -113,6 +133,15 @@
         entryLocation = entryLocation.replace(`/${VERSION}/`, '');
       }
       stack.push({name: entryName, location: entryLocation});
+    } // for (line in stack)
+
+    // Beautify the Wasm error message if possible. Most Wasm errors are of the
+    // form RuntimeError: unreachable or RuntimeError: abort. Those lead to bug
+    // titles that are undistinguishable from each other. Instead try using the
+    // first entry of the stack that contains a perfetto:: function name.
+    const wasmFunc = stack.find((e) => e.name.includes('perfetto::'))?.name;
+    if (errMsg.includes('RuntimeError') && wasmFunc) {
+      errMsg += ` @ ${wasmFunc.trim()}`;
     }
   }
   // Invoke all the handlers registered through addErrorHandler.
diff --git a/ui/src/frontend/error_dialog.ts b/ui/src/frontend/error_dialog.ts
index dd9a4a9..f2cea37 100644
--- a/ui/src/frontend/error_dialog.ts
+++ b/ui/src/frontend/error_dialog.ts
@@ -36,6 +36,8 @@
   // Here we rely on the exception message from onCannotGrowMemory function
   if (
     err.message.includes('Cannot enlarge memory') ||
+    err.stack.some((entry) => entry.name.includes('_emscripten_resize_heap')) ||
+    err.stack.some((entry) => entry.name.includes('sbrk')) ||
     /^out of memory$/m.exec(err.message)
   ) {
     showOutOfMemoryDialog();