addons/vscode/webview/uncaughtExceptions.tsxblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 */
b69ab317
b69ab318// Detect uncaught JavaScript errors and write them to the DOM in dev mode
b69ab319if (import.meta.hot) {
b69ab3110 let errorContainer: HTMLDivElement | null = null;
b69ab3111
b69ab3112 const createErrorContainer = () => {
b69ab3113 if (!errorContainer) {
b69ab3114 errorContainer = document.createElement('div');
b69ab3115 errorContainer.id = 'uncaught-error-display';
b69ab3116 errorContainer.style.cssText = `
b69ab3117 position: fixed;
b69ab3118 bottom: 0;
b69ab3119 left: 0;
b69ab3120 right: 0;
b69ab3121 max-height: 300px;
b69ab3122 background-color: rgba(220, 38, 38, 0.95);
b69ab3123 color: white;
b69ab3124 font-family: monospace;
b69ab3125 font-size: 12px;
b69ab3126 z-index: 999999;
b69ab3127 border-top: 3px solid #991b1b;
b69ab3128 display: flex;
b69ab3129 flex-direction: column;
b69ab3130 `;
b69ab3131
b69ab3132 // Create header row
b69ab3133 const header = document.createElement('div');
b69ab3134 header.style.cssText = `
b69ab3135 display: flex;
b69ab3136 justify-content: space-between;
b69ab3137 align-items: center;
b69ab3138 padding: 12px 16px;
b69ab3139 border-bottom: 1px solid rgba(255, 255, 255, 0.3);
b69ab3140 font-weight: bold;
b69ab3141 `;
b69ab3142
b69ab3143 const title = document.createElement('span');
b69ab3144 title.textContent = 'Uncaught Webview Errors (development mode only)';
b69ab3145 header.appendChild(title);
b69ab3146
b69ab3147 const closeButton = document.createElement('button');
b69ab3148 closeButton.textContent = '×';
b69ab3149 closeButton.style.cssText = `
b69ab3150 background: none;
b69ab3151 border: none;
b69ab3152 color: white;
b69ab3153 font-size: 24px;
b69ab3154 line-height: 1;
b69ab3155 cursor: pointer;
b69ab3156 padding: 0;
b69ab3157 width: 24px;
b69ab3158 height: 24px;
b69ab3159 display: flex;
b69ab3160 align-items: center;
b69ab3161 justify-content: center;
b69ab3162 `;
b69ab3163 closeButton.onclick = () => {
b69ab3164 if (errorContainer) {
b69ab3165 errorContainer.style.display = 'none';
b69ab3166 }
b69ab3167 };
b69ab3168 header.appendChild(closeButton);
b69ab3169
b69ab3170 // Create errors content container
b69ab3171 const errorsContent = document.createElement('div');
b69ab3172 errorsContent.id = 'uncaught-error-display-content';
b69ab3173 errorsContent.style.cssText = `
b69ab3174 overflow-y: auto;
b69ab3175 padding: 16px;
b69ab3176 flex: 1;
b69ab3177 `;
b69ab3178
b69ab3179 errorContainer.appendChild(header);
b69ab3180 errorContainer.appendChild(errorsContent);
b69ab3181 document.body.appendChild(errorContainer);
b69ab3182 }
b69ab3183 return errorContainer;
b69ab3184 };
b69ab3185
b69ab3186 const displayError = (
b69ab3187 message: string,
b69ab3188 source?: string,
b69ab3189 lineno?: number,
b69ab3190 colno?: number,
b69ab3191 error?: Error,
b69ab3192 ) => {
b69ab3193 const container = createErrorContainer();
b69ab3194 const errorsContent = container.querySelector('#uncaught-error-display-content');
b69ab3195 if (!errorsContent) {
b69ab3196 return;
b69ab3197 }
b69ab3198
b69ab3199 // Show the container if it was hidden
b69ab31100 container.style.display = 'flex';
b69ab31101
b69ab31102 const errorEntry = document.createElement('div');
b69ab31103 errorEntry.style.cssText = `
b69ab31104 margin-bottom: 12px;
b69ab31105 padding-bottom: 12px;
b69ab31106 border-bottom: 1px solid rgba(255, 255, 255, 0.3);
b69ab31107 `;
b69ab31108
b69ab31109 const timestamp = new Date().toLocaleTimeString();
b69ab31110
b69ab31111 // Create header line
b69ab31112 const header = document.createElement('strong');
b69ab31113 header.textContent = `[${timestamp}] Uncaught Error:`;
b69ab31114 errorEntry.appendChild(header);
b69ab31115 errorEntry.appendChild(document.createElement('br'));
b69ab31116
b69ab31117 // Add error message
b69ab31118 const messageText = document.createTextNode(message);
b69ab31119 errorEntry.appendChild(messageText);
b69ab31120 errorEntry.appendChild(document.createElement('br'));
b69ab31121
b69ab31122 // Add source location if available
b69ab31123 if (source) {
b69ab31124 const sourceSpan = document.createElement('span');
b69ab31125 sourceSpan.style.opacity = '0.8';
b69ab31126 sourceSpan.textContent = `at ${source}:${lineno}:${colno}`;
b69ab31127 errorEntry.appendChild(sourceSpan);
b69ab31128 errorEntry.appendChild(document.createElement('br'));
b69ab31129 }
b69ab31130
b69ab31131 // Add stack trace if available
b69ab31132 if (error?.stack) {
b69ab31133 const stackPre = document.createElement('pre');
b69ab31134 stackPre.style.cssText =
b69ab31135 'margin-top: 8px; opacity: 0.9; white-space: pre-wrap; word-break: break-all;';
b69ab31136 stackPre.textContent = error.stack;
b69ab31137 errorEntry.appendChild(stackPre);
b69ab31138 }
b69ab31139
b69ab31140 errorsContent.insertBefore(errorEntry, errorsContent.firstChild);
b69ab31141 };
b69ab31142
b69ab31143 const handleError = (event: ErrorEvent) => {
b69ab31144 displayError(event.message, event.filename, event.lineno, event.colno, event.error);
b69ab31145 };
b69ab31146
b69ab31147 const handleUnhandledRejection = (event: PromiseRejectionEvent) => {
b69ab31148 const error = event.reason;
b69ab31149 let message = 'Unhandled Promise Rejection';
b69ab31150 let errorObj: Error | undefined;
b69ab31151
b69ab31152 if (error instanceof Error) {
b69ab31153 message = error.message;
b69ab31154 errorObj = error;
b69ab31155 } else if (typeof error === 'string') {
b69ab31156 message = error;
b69ab31157 } else if (error != null) {
b69ab31158 message = String(error);
b69ab31159 }
b69ab31160
b69ab31161 displayError(`Promise: ${message}`, undefined, undefined, undefined, errorObj);
b69ab31162 };
b69ab31163
b69ab31164 window.addEventListener('error', handleError);
b69ab31165 window.addEventListener('unhandledrejection', handleUnhandledRejection);
b69ab31166
b69ab31167 // TODO: we should probably call this on hot reload somehow
b69ab31168 const _dispose = () => {
b69ab31169 window.removeEventListener('error', handleError);
b69ab31170 window.removeEventListener('unhandledrejection', handleUnhandledRejection);
b69ab31171 errorContainer?.remove();
b69ab31172 errorContainer = null;
b69ab31173 };
b69ab31174}