@@ -37,6 +37,13 @@ export function describeError(error, fallback) {
3737 return fallback + ( error === undefined || error === null ? '' : '\n' + stringifyValue ( error ) ) ;
3838}
3939
40+ // some error events don't mean that the client is broken: a ResizeObserver loop is simply retried
41+ // on the next frame and a cross-origin 'Script error.' usually comes from a browser extension.
42+ // both are reported without an Error object, which is what tells them apart from a real crash.
43+ export function isNonFatalError ( msg , error ) {
44+ return ! error && / ^ ( R e s i z e O b s e r v e r l o o p | S c r i p t e r r o r \. ? $ ) / . test ( `${ msg } ` ) ;
45+ }
46+
4047// a rejection reason is often a plain object like { status: 500 } - String() would turn that
4148// into a useless [object Object], while JSON.stringify fails on cyclic values and BigInt
4249function stringifyValue ( value ) {
@@ -76,7 +83,18 @@ onLoad(function() {
7683 showOverlay ( 'clientErrorOverlay' ) ;
7784 }
7885
86+ // the status line asks the user to copy the technical details, so make sure they are visible
87+ const askForManualReport = function ( ) {
88+ $ ( '#clientErrorStatus' ) . style . display = '' ;
89+ $ ( '#clientErrorOverlay details' ) . open = true ;
90+ }
91+
7992 const reportError = function ( description ) {
93+ // close the connection before collecting the context: if that throws, the user still ends up
94+ // with a terminal overlay over a terminated session instead of a still running one
95+ preventReconnect ( ) ;
96+ connection . close ( ) ;
97+
8098 const details = {
8199 error : description ,
82100 undoProtocol,
@@ -95,16 +113,14 @@ onLoad(function() {
95113 playerName,
96114 html : document . documentElement . outerHTML
97115 } ;
98- preventReconnect ( ) ;
99- connection . close ( ) ;
100116
101117 const button = $ ( '#clientErrorOverlay button' ) ;
102118 // what the user typed is the most valuable part of the report - keep the textarea intact
103119 // and put the reason for the failure into the technical details instead
104120 const submitFailed = function ( reason ) {
105121 button . disabled = false ;
106122 button . textContent = 'Try again' ;
107- $ ( '#clientErrorStatus' ) . style . display = '' ;
123+ askForManualReport ( ) ;
108124 $ ( '#clientErrorStack' ) . textContent = `${ details . error } \n\nSubmitting the report failed:\n${ reason } ` ;
109125 }
110126
@@ -158,7 +174,7 @@ onLoad(function() {
158174 // is nothing to submit in that case, so ask for a manual report and offer a plain reload.
159175 $ ( '#clientErrorQuestion' ) . style . display = 'none' ;
160176 $ ( '#clientErrorInput' ) . style . display = 'none' ;
161- $ ( '#clientErrorStatus' ) . style . display = '' ;
177+ askForManualReport ( ) ;
162178 const button = $ ( '#clientErrorOverlay button' ) ;
163179 button . textContent = 'Reload' ;
164180 button . addEventListener ( 'click' , _ => window . location . reload ( ) ) ;
@@ -167,8 +183,13 @@ onLoad(function() {
167183 }
168184
169185 window . onerror = function ( msg , url , line , col , err ) {
170- // browsers report cross-origin errors without a location - 'at :0:0' would just look broken
171- errorHandler ( err , `${ msg } ` + ( url ? `\n at ${ url } :${ line } :${ col } ` : '' ) ) ;
186+ // tearing the session down over a non-fatal event would be worse than ignoring it, which is
187+ // what happened anyway before this handler learned to survive a missing Error object
188+ if ( isNonFatalError ( msg , err ) )
189+ return ;
190+ // when the browser has no real location it passes the document URL with line and column 0 -
191+ // reporting 'at <page>:0:0' would just look like a truncation bug, so leave the line out
192+ errorHandler ( err , `${ msg } ` + ( url && line ? `\n at ${ url } :${ line } :${ col } ` : '' ) ) ;
172193 } ;
173194 window . addEventListener ( "unhandledrejection" , function ( promiseRejectionEvent ) {
174195 errorHandler ( promiseRejectionEvent . reason , 'Unhandled promise rejection' ) ;
0 commit comments