]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/xul/staff_client/chrome/content/util/error.js
xul_param and modal xulG conversion
[Evergreen.git] / Open-ILS / xul / staff_client / chrome / content / util / error.js
1 dump('entering util/error.js\n');
2
3 if (typeof util == 'undefined') util = {};
4 util.error = function () {
5
6         try {
7                 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
8                 this.consoleService = Components.classes['@mozilla.org/consoleservice;1']
9                         .getService(Components.interfaces.nsIConsoleService);
10         } catch(E) {
11                 this.consoleDump = false;
12                 dump('util.error constructor: ' + E + '\n');
13         }
14
15         this.sdump_last_time = new Date();
16
17         this.OpenILS = {};
18
19         JSAN.use('util.sound'); this.sound = new util.sound();
20
21         return this;
22 };
23
24 util.error.prototype = {
25
26         'printDebug' : true,
27         'consoleDump' : false,
28         'debugDump' : true,
29         'fileDump' : false,
30         'alertDump' : false,
31         'arg_dump_full' : false,
32
33         'debug' : function(e){
34                 dump('-----------------------------------------\n' 
35                         + e + '\n-----------------------------------------\n' );
36         },
37
38         'sdump_levels' : {
39
40                 'D_NONE' : false, 
41                 'D_ALL' : false, 
42                 'D_ERROR' : { 'dump' : true, 'console' : true }, 
43                 'D_DEBUG' : { 'dump' : true, 'console' : true }, 
44                 'D_TRACE' :  { 'dump' : true }, 
45                 'D_ALERT' : { 'alert' : true, 'dump' : true },
46                 'D_WARN' : false, 
47                 'D_XULRUNNER' : false, 
48                 'D_DECK' : { 'dump' : true },
49                 'D_TRACE_ENTER' :  false, 
50                 'D_TRACE_EXIT' :  false, 
51                 'D_TIMEOUT' :  false, 
52                 'D_FILTER' : false,
53                 'D_CONSTRUCTOR' : false, 
54                 'D_FIREFOX' : false, 
55                 'D_LEGACY' : false, 
56                 'D_DATA_STASH' : { 'alert' : false }, 
57                 'D_DATA_RETRIEVE' : false,
58
59                 'D_CLAM' : false, 
60                 'D_PAGED_TREE' : false, 
61                 'D_GRID_LIST' : false, 
62                 'D_HTML_TABLE' : false,
63                 'D_TAB' : false, 
64                 'D_LIST' : false, 
65                 'D_LIST_DUMP_WITH_KEYS_ON_CLEAR' : false, 
66                 'D_LIST_DUMP_ON_CLEAR' : false,
67
68                 'D_AUTH' : { 'dump' : true }, 
69                 'D_OPAC' : { 'dump' : true }, 
70                 'D_CAT' : false, 
71                 'D_BROWSER' : { 'dump' : true },
72
73                 'D_PATRON_SEARCH' : false, 
74                 'D_PATRON_SEARCH_FORM' : false, 
75                 'D_PATRON_SEARCH_RESULTS' : false,
76
77                 'D_PATRON_DISPLAY' : false, 
78                 'D_PATRON_DISPLAY_STATUS' : false, 
79                 'D_PATRON_DISPLAY_CONTACT' : false,
80
81                 'D_PATRON_ITEMS' : false, 
82                 'D_PATRON_CHECKOUT_ITEMS' : false, 
83                 'D_PATRON_HOLDS' : false,
84                 'D_PATRON_BILLS' : false, 
85                 'D_PATRON_EDIT' : false,
86
87                 'D_CHECKIN' : false, 
88                 'D_CHECKIN_ITEMS' : false,
89
90                 'D_HOLD_CAPTURE' : false, 
91                 'D_HOLD_CAPTURE_ITEMS' : false,
92
93                 'D_PATRON_UTILS' : false, 
94                 'D_CIRC_UTILS' : false,
95
96                 'D_FILE' : false, 
97                 'D_EXPLODE' : false, 
98                 'D_FM_UTILS' : false, 
99                 'D_PRINT' : { 'dump' : true }, 
100                 'D_OBSERVERS' : { 'dump' : true, 'console' : false },
101                 'D_CACHE' : { 'dump' : true, 'console' : false },
102                 'D_SES' : { 'dump' : true, 'console' : false },
103                 'D_SES_FUNC' : false, 
104                 'D_SES_RESULT' : { 'dump' : true }, 
105                 'D_SES_ERROR' : { 'dump' : true, 'console' : true }, 
106                 'D_SPAWN' : false, 
107                 'D_STRING' : false,
108                 'D_UTIL' : false, 
109                 'D_WIN' : { 'dump' : true }, 
110                 'D_WIDGETS' : false
111         },
112
113         'filter_console_init' : function (p) {
114                 this.sdump('D_FILTER',this.arg_dump(arguments,{0:true}));
115
116                 var filterConsoleListener = {
117                         observe: function( msg ) {
118                                 try {
119                                         p.observe_msg( msg );
120                                 } catch(E) {
121                                         alert(E);
122                                 }
123                         },
124                         QueryInterface: function (iid) {
125                                 if (!iid.equals(Components.interfaces.nsIConsoleListener) &&
126                                         !iid.equals(Components.interfaces.nsISupports)) {
127                                                 throw Components.results.NS_ERROR_NO_INTERFACE;
128                                 }
129                                 return this;
130                         }
131                 };
132                 try {
133                         this.consoleService.registerListener(filterConsoleListener);    
134                 } catch(E) {
135                         alert(E);
136                 }
137
138                 this.sdump('D_TRACE_EXIT',this.arg_dump(arguments));
139         },
140
141         'sdump' : function (level,msg) {
142                 try {
143                         var now = new Date();
144                         var message = now.valueOf() + '\tdelta = ' + (now.valueOf() - this.sdump_last_time.valueOf()) + '\t' + level + '\n' + msg;
145                         if (this.sdump_levels['D_NONE']) return null;
146                         if (this.sdump_levels[level]||this.sdump_levels['D_ALL']) {
147                                 this.sdump_last_time = now;
148                                 if (this.debugDump || ( this.sdump_levels[level] && this.sdump_levels[level].debug ) ) this.debug(message);
149                                 if (this.alertDump || ( this.sdump_levels[level] && this.sdump_levels[level].alert ) ) alert(message);
150                                 if (this.consoleDump || ( this.sdump_levels[level] && this.sdump_levels[level].console ) ) {
151                                         netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
152                                         this.consoleService.logStringMessage(message);
153                                 }
154                                 if (this.fileDump || ( this.sdump_levels[level] && this.sdump_levels[level].file ) ) {
155                                         if (level!='D_FILE') {
156                                                 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
157                                                 JSAN.use('util.file'); var master_log = new util.file('log');
158                                                 master_log.write_content('append',message); master_log.close();
159                                                 var specific_log = new util.file('log_'+level);
160                                                 specific_log.write_content('append',message); specific_log.close();
161                                         }
162                                 }
163                         }
164                 } catch(E) {
165                         dump('Calling sdump but ' + E + '\n');
166                 }
167         },
168
169         'arg_dump' : function (args,dump_these) {
170                 var s = '*>*>*> Called function ';
171                 try {
172                         if (!dump_these)
173                                 dump_these = {};
174                         s += args.callee.toString().match(/\w+/g)[1] + ' : ';
175                         for (var i = 0; i < args.length; i++)
176                                 s += typeof(args[i]) + ' ';
177                         s += '\n';
178                         for (var i = 0; i < args.length; i++)
179                                 if (dump_these[i]) {
180
181                                         var arg = args[i];
182                                         //dump('dump_these[i] = ' + dump_these[i] + '  arg = ' + arg + '\n');
183
184                                         if (typeof(dump_these[i])=='string') {
185
186                                                 if (dump_these[i].slice(0,1) == '.') {
187                                                         var cmd = 'arg' + dump_these[i];
188                                                         var result;
189                                                         try {
190                                                                 result = eval( cmd );
191                                                         } catch(E) {
192                                                                 result = cmd + ' ==> ' + E;
193                                                         }
194                                                         s += '\targ #' + i + ': ' + cmd + ' = ' + result;
195                                                 } else {
196                                                         var result;
197                                                         try {
198                                                                 result = eval( dump_these[i] );
199                                                         } catch(E) {
200                                                                 result = dump_these[i] + ' ==> ' + E;
201                                                         }
202                                                         s += '\targ #' + i + ': ' + result;
203                                                 }
204         
205                                         } else {
206                                                 s += '\targ #' + i + ' = ';
207                                                 try {
208                                                         //s += js2JSON( arg );
209                                                         s += arg;
210                                                 } catch(E) {
211                                                         s += arg;
212                                                 }
213                                         }
214         
215                                         s += '\n';
216                                         if (this.arg_dump_full)
217                                                 s += 'Definition: ' + args.callee.toString() + '\n';
218         
219                                 }
220                         return s;
221                 } catch(E) {
222                         return s + '\nDEBUG ME: ' + js2JSON(E) + '\n';
223                 }
224         },
225
226         'handle_error' : function (E,annoy) {
227                 var s = '';
228                 if (instanceOf(E,ex)) {
229                         s += E.err_msg();
230                         //s += '\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n';
231                         //s += 'This error was anticipated.\n\n';
232                         //s += js2JSON(E).substr(0,200) + '...\n\n';
233                         if (snd_bad) snd_bad();
234                 } else {
235                         s += '\n\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n';
236                         s += 'This is a bug that we will fix later.\n\n';
237                         try {
238                                 s += js2JSON(E).substr(0,1024) + '\n\n';
239                         } catch(E2) {
240                                 try {
241                                         s += E.substr(0,1024) + '\n\n';
242                                 } catch(E3) {
243                                         s += E + '\n\n';
244                                 }
245                         }
246                         if (snd_really_bad) snd_really_bad();
247                 }
248                 sdump('D_ERROR',s);
249                 if (annoy)
250                         this.s_alert(s);
251                 else
252                         alert(s);
253         },
254
255         's_alert' : function (s) { alert(s); },
256
257         'get_ilsevent' : function(status) {
258                 JSAN.use('OpenILS.data'); 
259                 this.OpenILS.data = new OpenILS.data(); this.OpenILS.data.init({'via':'stash'});
260                 return this.OpenILS.data.entities['ilsevent.'+status];
261         },
262
263         'standard_network_error_alert' : function(msg) {
264                 var obj = this;
265                 if (!msg) msg = '';
266                 var alert_msg = 'We experienced a network/server communication failure.  Please check your internet connection and try this action again.  Repeated failures may require attention from your local IT staff or your friendly Evergreen developers.\n\n' + msg.substr(0,100);
267                 obj.yns_alert(
268                         alert_msg,      
269                         'Communication Failure',
270                         'Ok', null, null, 'Check here to confirm this message'
271                 );
272         },
273
274         'standard_unexpected_error_alert' : function(msg,E) {
275                 var obj = this;
276                 if (typeof E.ilsevent != 'undefined') {
277                         if (E.ilsevent == 0 /* SUCCESS */ ) {
278                                 msg = "The action involved likely succeeded, however, this part of the software needs to be updated to better understand success messages from the server, so please let us know about it.";
279                         }
280                         if (E.ilsevent == -1 /* Network/Server Problem */ ) {
281                                 return obj.standard_network_error_alert(msg);
282                         }
283                         if (E.ilsevent == 5000 /* PERM_FAILURE */ ) {
284                                 msg = "The action involved likely failed due to insufficient permissions.  However, this part of the software needs to be updated to better understand permission messages from the server, so please let us know about it.";
285                         }
286                 }
287                 if (!msg) msg = '';
288                 var alert_msg = 'FIXME:  If you encounter this alert, please inform your IT/ILS helpdesk staff or your friendly Evergreen developers.\n\n' + (new Date()) + '\n\n' + msg + '\n\n' + (typeof E.ilsevent != 'undefined' ? E.textcode + '\n' + (E.desc ? E.desc + '\n' : '') : '') + ( typeof E.status != 'undefined' ? 'Status: ' + E.status + '\n': '' );
289                 obj.sdump('D_ERROR',msg + ' : ' + js2JSON(E));
290                 var r = obj.yns_alert(
291                         alert_msg,      
292                         'Unhandled Error',
293                         'Ok', 'Debug Output to send to Helpdesk', null, 'Check here to confirm this message',
294                         '/xul/server/skin/media/images/skull.png'
295                 );
296                 if (r == 1) {
297                         JSAN.use('util.window'); var win = new util.window();
298                         win.open(
299                                 'data:text/plain,' + window.escape( 'Please open a helpdesk ticket and include the following text: \n\n' + (new Date()) + '\n\n' + msg + '\n\n' + obj.pretty_print(js2JSON(E)) ),
300                                 'error_alert',
301                                 'chrome,resizable,width=700,height=500'
302                         );
303                 }
304                 if (r==2) {
305                         alert('Not Yet Implemented');
306                 }
307         },
308
309         'yns_alert' : function (s,title,b1,b2,b3,c,image) {
310
311                 try {
312
313                         if (location.href.match(/^chrome/)) return this.yns_alert_original(s,title,b1,b2,b3,c);
314
315                 /* The original purpose of yns_alert was to prevent errors from being scanned through accidentally with a barcode scanner.  
316                 However, this can be done in a less annoying manner by rolling our own dialog and not having any of the options in focus */
317
318                 /*
319                         s       = Message to display
320                         title   = Text in Title Bar
321                         b1      = Text for button 1
322                         b2      = Text for button 2
323                         b3      = Text for button 3
324                         c       = Text for confirmation checkbox.  null for no confirm
325                 */
326
327                 dump('yns_alert:\n\ts = ' + s + '\n\ttitle = ' + title + '\n\tb1 = ' + b1 + '\n\tb2 = ' + b2 + '\n\tb3 = ' + b3 + '\n\tc = ' + c + '\n');
328                 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect UniversalBrowserWrite");
329
330                 this.sound.bad();
331
332
333                 //FIMXE - is that good enough of an escape job?
334                 s = s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
335
336                 var xml = '<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" flex="1">' 
337                         + '<groupbox flex="1" style="overflow: auto; border: solid thin red;"><caption label="' + (title) + '"/>';
338
339                 if (image) xml += '<hbox><image src="' + image + '"/><spacer flex="1"/></hbox>';
340                 xml += '<description style="font-size: large">' + (s)
341                         + '</description></groupbox><groupbox><caption label="Options"/><hbox>';
342                 var b1_key = b1 ? b1[0] : '';
343                 var b2_key = b2 ? b2[0] : '';
344                 var b3_key = b3 ? b3[0] : ''; /* FIXME - need to check for collisions */
345                 if (b1) xml += '<button id="b1" accesskey="' + b1_key + '" label="' + (b1) + '" name="fancy_submit" value="b1"/>'
346                 if (b2) xml += '<button id="b2" accesskey="' + b2_key + '" label="' + (b2) + '" name="fancy_submit" value="b2"/>'
347                 if (b3) xml += '<button id="b3" accesskey="' + b3_key + '" label="' + (b3) + '" name="fancy_submit" value="b3"/>'
348                 xml += '</hbox></groupbox></vbox>';
349                 JSAN.use('OpenILS.data');
350                 //var data = new OpenILS.data(); data.init({'via':'stash'});
351                 //data.temp_yns_xml = xml; data.stash('temp_yns_xml');
352                 var url = urls.XUL_FANCY_PROMPT; // + '?xml_in_stash=temp_yns_xml' + '&title=' + window.escape(title);
353                 if (typeof xulG != 'undefined') if (typeof xulG.url_prefix == 'function') url = xulG.url_prefix( url );
354                 JSAN.use('util.window'); var win = new util.window();
355                 var fancy_prompt_data = win.open(
356                         url, 'fancy_prompt', 'chrome,resizable,modal,width=700,height=500', { 'xml' : xml, 'title' : title }
357                 );
358                 if (fancy_prompt_data.fancy_status == 'complete') {
359                         switch(fancy_prompt_data.fancy_submit) {
360                                 case 'b1' : return 0; break;
361                                 case 'b2' : return 1; break;
362                                 case 'b3' : return 2; break;
363                         }
364                 } else {
365                         //return this.yns_alert(s,title,b1,b2,b3,c,image);
366                         return null;
367                 }
368
369                 } catch(E) {
370
371                         dump('yns_alert failed: ' + E + '\ns = ' + s + '\ntitle = ' + title + '\nb1 = ' + b1 + '\nb2 = ' + b2 + '\nb3 = ' + b3 + '\nc = ' + c + '\nimage = ' + image + '\n');
372
373                         this.yns_alert_original(s + '\n\nAlso, yns_alert failed: ' + E,title,b1,b2,b3,c);
374
375                 }
376         },
377
378         'yns_alert_formatted' : function (s,title,b1,b2,b3,c,image) {
379
380                 try {
381
382                         if (location.href.match(/^chrome/)) return this.yns_alert_original(s,title,b1,b2,b3,c);
383
384                 /* The original purpose of yns_alert was to prevent errors from being scanned through accidentally with a barcode scanner.  
385                 However, this can be done in a less annoying manner by rolling our own dialog and not having any of the options in focus */
386
387                 /*
388                         s       = Message to display
389                         title   = Text in Title Bar
390                         b1      = Text for button 1
391                         b2      = Text for button 2
392                         b3      = Text for button 3
393                         c       = Text for confirmation checkbox.  null for no confirm
394                 */
395
396                 dump('yns_alert:\n\ts = ' + s + '\n\ttitle = ' + title + '\n\tb1 = ' + b1 + '\n\tb2 = ' + b2 + '\n\tb3 = ' + b3 + '\n\tc = ' + c + '\n');
397                 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect UniversalBrowserWrite");
398
399                 this.sound.bad();
400
401
402                 //FIMXE - is that good enough of an escape job?
403                 s = s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
404
405                 var xml = '<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml" flex="1">' 
406                         + '<groupbox flex="1" style="overflow: auto; border: solid thin red;"><caption label="' + (title) + '"/>';
407
408                 if (image) xml += '<hbox><image src="' + image + '"/><spacer flex="1"/></hbox>';
409                 xml += '<description style="font-size: large"><html:pre style="font-size: large">' + (s)
410                         + '</html:pre></description></groupbox><groupbox><caption label="Options"/><hbox>';
411                 var b1_key = b1 ? b1[0] : '';
412                 var b2_key = b2 ? b2[0] : '';
413                 var b3_key = b3 ? b3[0] : ''; /* FIXME - need to check for collisions */
414                 if (b1) xml += '<button id="b1" accesskey="' + b1_key + '" label="' + (b1) + '" name="fancy_submit" value="b1"/>'
415                 if (b2) xml += '<button id="b2" accesskey="' + b2_key + '" label="' + (b2) + '" name="fancy_submit" value="b2"/>'
416                 if (b3) xml += '<button id="b3" accesskey="' + b3_key + '" label="' + (b3) + '" name="fancy_submit" value="b3"/>'
417                 xml += '</hbox></groupbox></vbox>';
418                 JSAN.use('OpenILS.data');
419                 //var data = new OpenILS.data(); data.init({'via':'stash'});
420                 //data.temp_yns_xml = xml; data.stash('temp_yns_xml');
421                 var url = urls.XUL_FANCY_PROMPT; // + '?xml_in_stash=temp_yns_xml' + '&title=' + window.escape(title);
422                 if (typeof xulG != 'undefined') if (typeof xulG.url_prefix == 'function') url = xulG.url_prefix( url );
423                 JSAN.use('util.window'); var win = new util.window();
424                 var fancy_prompt_data = win.open(
425                         url, 'fancy_prompt', 'chrome,resizable,modal,width=700,height=500', { 'xml' : xml, 'title' : title }
426                 );
427                 if (fancy_prompt_data == 'complete') {
428                         switch(fancy_prompt_data.fancy_submit) {
429                                 case 'b1' : return 0; break;
430                                 case 'b2' : return 1; break;
431                                 case 'b3' : return 2; break;
432                         }
433                 } else {
434                         //return this.yns_alert(s,title,b1,b2,b3,c,image);
435                         return null;
436                 }
437
438                 } catch(E) {
439
440                         alert('yns_alert_formatted failed: ' + E + '\ns = ' + s + '\ntitle = ' + title + '\nb1 = ' + b1 + '\nb2 = ' + b2 + '\nb3 = ' + b3 + '\nc = ' + c + '\nimage = ' + image + '\n');
441
442                 }
443
444         },
445
446         'yns_alert_original' : function (s,title,b1,b2,b3,c) {
447
448                 /*
449                         s       = Message to display
450                         title   = Text in Title Bar
451                         b1      = Text for button 1
452                         b2      = Text for button 2
453                         b3      = Text for button 3
454                         c       = Text for confirmation checkbox.  null for no confirm
455                 */
456
457                 dump('yns_alert:\n\ts = ' + s + '\n\ttitle = ' + title + '\n\tb1 = ' + b1 + '\n\tb2 = ' + b2 + '\n\tb3 = ' + b3 + '\n\tc = ' + c + '\n');
458                 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
459
460                 this.sound.bad();
461
462                 // get a reference to the prompt service component.
463                 var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
464                         .getService(Components.interfaces.nsIPromptService);
465
466                 // set the buttons that will appear on the dialog. It should be
467                 // a set of constants multiplied by button position constants. In this case,
468                 // three buttons appear, Save, Cancel and a custom button.
469                 //var flags=promptService.BUTTON_TITLE_OK * promptService.BUTTON_POS_0 +
470                 //      promptService.BUTTON_TITLE_CANCEL * promptService.BUTTON_POS_1 +
471                 //      promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_2;
472                 var flags = promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_0 +
473                         promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_1 +
474                         promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_2; 
475
476                 // display the dialog box. The flags set above are passed
477                 // as the fourth argument. The next three arguments are custom labels used for
478                 // the buttons, which are used if BUTTON_TITLE_IS_STRING is assigned to a
479                 // particular button. The last two arguments are for an optional check box.
480                 var check = {};
481                 var rv = promptService.confirmEx(window,title, s, flags, b1, b2, b3, c, check);
482                 if (c && !check.value) {
483                         return this.yns_alert(s,title,b1,b2,b3,c);
484                 }
485                 return rv;
486         },
487
488         'print_tabs' : function(t) {
489                 var r = '';
490                 for (var j = 0; j < t; j++ ) { r = r + "\t"; }
491                 return r;
492         },
493
494         'pretty_print' : function(s) {
495                 var r = ''; var t = 0;
496                 for (var i in s) {
497                         if (s[i] == '{') {
498                                 r = r + "\n" + this.print_tabs(t) + s[i]; t++;
499                                 r = r + "\n" + this.print_tabs(t);
500                         } else if (s[i] == '[') {
501                                 r = r + "\n" + this.print_tabs(t) + s[i]; t++;
502                                 r = r + "\n" + this.print_tabs(t);
503                         } else if (s[i] == '}') {
504                                 t--; r = r + "\n" + this.print_tabs(t) + s[i];
505                                 r = r + "\n" + this.print_tabs(t);
506                         } else if (s[i] == ']') {
507                                 t--; r = r + "\n" + this.print_tabs(t) + s[i];
508                                 r = r + "\n" + this.print_tabs(t);
509                         } else if (s[i] == ',') {
510                                 r = r + s[i];
511                                 r = r + "\n" + this.print_tabs(t);
512                         } else {
513                                 r = r + s[i];
514                         }
515                 }
516                 return r;
517         },
518 }
519
520 dump('exiting util/error.js\n');