Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/third_party/closure/goog/debug/debug.js
3991 views
1
/**
2
* @license
3
* Copyright The Closure Library Authors.
4
* SPDX-License-Identifier: Apache-2.0
5
*/
6
7
/**
8
* @fileoverview Logging and debugging utilities.
9
*
10
* @see ../demos/debug.html
11
*/
12
13
goog.provide('goog.debug');
14
15
goog.require('goog.array');
16
goog.require('goog.debug.errorcontext');
17
18
goog.require('goog.utils');
19
20
21
/** @define {boolean} Whether logging should be enabled. */
22
goog.debug.LOGGING_ENABLED =
23
goog.define('goog.debug.LOGGING_ENABLED', goog.DEBUG);
24
25
26
/** @define {boolean} Whether to force "sloppy" stack building. */
27
goog.debug.FORCE_SLOPPY_STACKS =
28
goog.define('goog.debug.FORCE_SLOPPY_STACKS', false);
29
30
31
/**
32
* @define {boolean} TODO(user): Remove this hack once bug is resolved.
33
*/
34
goog.debug.CHECK_FOR_THROWN_EVENT =
35
goog.define('goog.debug.CHECK_FOR_THROWN_EVENT', false);
36
37
38
39
/**
40
* Catches onerror events fired by windows and similar objects.
41
* @param {function(Object)} logFunc The function to call with the error
42
* information.
43
* @param {boolean=} opt_cancel Whether to stop the error from reaching the
44
* browser.
45
* @param {Object=} opt_target Object that fires onerror events.
46
* @suppress {strictMissingProperties} onerror is not defined as a property
47
* on Object.
48
*/
49
goog.debug.catchErrors = function(logFunc, opt_cancel, opt_target) {
50
'use strict';
51
var target = opt_target || goog.global;
52
var oldErrorHandler = target.onerror;
53
var retVal = !!opt_cancel;
54
55
/**
56
* New onerror handler for this target. This onerror handler follows the spec
57
* according to
58
* http://www.whatwg.org/specs/web-apps/current-work/#runtime-script-errors
59
* The spec was changed in August 2013 to support receiving column information
60
* and an error object for all scripts on the same origin or cross origin
61
* scripts with the proper headers. See
62
* https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror
63
*
64
* @param {string} message The error message. For cross-origin errors, this
65
* will be scrubbed to just "Script error.". For new browsers that have
66
* updated to follow the latest spec, errors that come from origins that
67
* have proper cross origin headers will not be scrubbed.
68
* @param {string} url The URL of the script that caused the error. The URL
69
* will be scrubbed to "" for cross origin scripts unless the script has
70
* proper cross origin headers and the browser has updated to the latest
71
* spec.
72
* @param {number} line The line number in the script that the error
73
* occurred on.
74
* @param {number=} opt_col The optional column number that the error
75
* occurred on. Only browsers that have updated to the latest spec will
76
* include this.
77
* @param {Error=} opt_error The optional actual error object for this
78
* error that should include the stack. Only browsers that have updated
79
* to the latest spec will inlude this parameter.
80
* @return {boolean} Whether to prevent the error from reaching the browser.
81
*/
82
target.onerror = function(message, url, line, opt_col, opt_error) {
83
'use strict';
84
if (oldErrorHandler) {
85
oldErrorHandler(message, url, line, opt_col, opt_error);
86
}
87
logFunc({
88
message: message,
89
fileName: url,
90
line: line,
91
lineNumber: line,
92
col: opt_col,
93
error: opt_error
94
});
95
return retVal;
96
};
97
};
98
99
100
/**
101
* Creates a string representing an object and all its properties.
102
* @param {Object|null|undefined} obj Object to expose.
103
* @param {boolean=} opt_showFn Show the functions as well as the properties,
104
* default is false.
105
* @return {string} The string representation of `obj`.
106
*/
107
goog.debug.expose = function(obj, opt_showFn) {
108
'use strict';
109
if (typeof obj == 'undefined') {
110
return 'undefined';
111
}
112
if (obj == null) {
113
return 'NULL';
114
}
115
var str = [];
116
117
for (var x in obj) {
118
if (!opt_showFn && typeof obj[x] === 'function') {
119
continue;
120
}
121
var s = x + ' = ';
122
123
try {
124
s += obj[x];
125
} catch (e) {
126
s += '*** ' + e + ' ***';
127
}
128
str.push(s);
129
}
130
return str.join('\n');
131
};
132
133
134
/**
135
* Creates a string representing a given primitive or object, and for an
136
* object, all its properties and nested objects. NOTE: The output will include
137
* Uids on all objects that were exposed. Any added Uids will be removed before
138
* returning.
139
* @param {*} obj Object to expose.
140
* @param {boolean=} opt_showFn Also show properties that are functions (by
141
* default, functions are omitted).
142
* @return {string} A string representation of `obj`.
143
*/
144
goog.debug.deepExpose = function(obj, opt_showFn) {
145
'use strict';
146
var str = [];
147
148
// Track any objects where deepExpose added a Uid, so they can be cleaned up
149
// before return. We do this globally, rather than only on ancestors so that
150
// if the same object appears in the output, you can see it.
151
var uidsToCleanup = [];
152
var ancestorUids = {};
153
154
var helper = function(obj, space) {
155
'use strict';
156
var nestspace = space + ' ';
157
158
var indentMultiline = function(str) {
159
'use strict';
160
return str.replace(/\n/g, '\n' + space);
161
};
162
163
164
try {
165
if (obj === undefined) {
166
str.push('undefined');
167
} else if (obj === null) {
168
str.push('NULL');
169
} else if (typeof obj === 'string') {
170
str.push('"' + indentMultiline(obj) + '"');
171
} else if (typeof obj === 'function') {
172
str.push(indentMultiline(String(obj)));
173
} else if (goog.utils.isObject(obj)) {
174
// Add a Uid if needed. The struct calls implicitly adds them.
175
if (!goog.utils.hasUid(obj)) {
176
uidsToCleanup.push(obj);
177
}
178
var uid = goog.utils.getUid(obj);
179
if (ancestorUids[uid]) {
180
str.push('*** reference loop detected (id=' + uid + ') ***');
181
} else {
182
ancestorUids[uid] = true;
183
str.push('{');
184
for (var x in obj) {
185
if (!opt_showFn && typeof obj[x] === 'function') {
186
continue;
187
}
188
str.push('\n');
189
str.push(nestspace);
190
str.push(x + ' = ');
191
helper(obj[x], nestspace);
192
}
193
str.push('\n' + space + '}');
194
delete ancestorUids[uid];
195
}
196
} else {
197
str.push(obj);
198
}
199
} catch (e) {
200
str.push('*** ' + e + ' ***');
201
}
202
};
203
204
helper(obj, '');
205
206
// Cleanup any Uids that were added by the deepExpose.
207
for (var i = 0; i < uidsToCleanup.length; i++) {
208
goog.utils.removeUid(uidsToCleanup[i]);
209
}
210
211
return str.join('');
212
};
213
214
215
/**
216
* Recursively outputs a nested array as a string.
217
* @param {Array<?>} arr The array.
218
* @return {string} String representing nested array.
219
*/
220
goog.debug.exposeArray = function(arr) {
221
'use strict';
222
var str = [];
223
for (var i = 0; i < arr.length; i++) {
224
if (Array.isArray(arr[i])) {
225
str.push(goog.debug.exposeArray(arr[i]));
226
} else {
227
str.push(arr[i]);
228
}
229
}
230
return '[ ' + str.join(', ') + ' ]';
231
};
232
233
234
/**
235
* Normalizes the error/exception object between browsers.
236
* @param {*} err Raw error object.
237
* @return {{
238
* message: (?|undefined),
239
* name: (?|undefined),
240
* lineNumber: (?|undefined),
241
* fileName: (?|undefined),
242
* stack: (?|undefined)
243
* }} Representation of err as an Object. It will never return err.
244
* @suppress {strictMissingProperties} properties not defined on err
245
*/
246
goog.debug.normalizeErrorObject = function(err) {
247
'use strict';
248
var href = goog.getObjectByName('window.location.href');
249
if (err == null) {
250
err = 'Unknown Error of type "null/undefined"';
251
}
252
if (typeof err === 'string') {
253
return {
254
'message': err,
255
'name': 'Unknown error',
256
'lineNumber': 'Not available',
257
'fileName': href,
258
'stack': 'Not available'
259
};
260
}
261
262
var lineNumber, fileName;
263
var threwError = false;
264
265
try {
266
lineNumber = err.lineNumber || err.line || 'Not available';
267
} catch (e) {
268
// Firefox 2 sometimes throws an error when accessing 'lineNumber':
269
// Message: Permission denied to get property UnnamedClass.lineNumber
270
lineNumber = 'Not available';
271
threwError = true;
272
}
273
274
try {
275
fileName = err.fileName || err.filename || err.sourceURL ||
276
// $googDebugFname may be set before a call to eval to set the filename
277
// that the eval is supposed to present.
278
goog.global['$googDebugFname'] || href;
279
} catch (e) {
280
// Firefox 2 may also throw an error when accessing 'filename'.
281
fileName = 'Not available';
282
threwError = true;
283
}
284
285
var stack = goog.debug.serializeErrorStack_(err);
286
287
// The IE Error object contains only the name and the message.
288
// The Safari Error object uses the line and sourceURL fields.
289
if (threwError || !err.lineNumber || !err.fileName || !err.stack ||
290
!err.message || !err.name) {
291
var message = err.message;
292
if (message == null) {
293
if (err.constructor && err.constructor instanceof Function) {
294
var ctorName = err.constructor.name ?
295
err.constructor.name :
296
goog.debug.getFunctionName(err.constructor);
297
message = 'Unknown Error of type "' + ctorName + '"';
298
// TODO(user): Remove this hack once bug is resolved.
299
if (goog.debug.CHECK_FOR_THROWN_EVENT && ctorName == 'Event') {
300
try {
301
message = message + ' with Event.type "' + (err.type || '') + '"';
302
} catch (e) {
303
// Just give up on getting more information out of the error object.
304
}
305
}
306
} else {
307
message = 'Unknown Error of unknown type';
308
}
309
310
// Avoid TypeError since toString could be missing from the instance
311
// (e.g. if created Object.create(null)).
312
if (typeof err.toString === 'function' &&
313
Object.prototype.toString !== err.toString) {
314
message += ': ' + err.toString();
315
}
316
}
317
return {
318
'message': message,
319
'name': err.name || 'UnknownError',
320
'lineNumber': lineNumber,
321
'fileName': fileName,
322
'stack': stack || 'Not available'
323
};
324
}
325
// Standards error object
326
// Typed !Object. Should be a subtype of the return type, but it's not.
327
err.stack = stack;
328
329
// Return non-standard error to allow for consistent result (eg. enumerable).
330
return {
331
'message': err.message,
332
'name': err.name,
333
'lineNumber': err.lineNumber,
334
'fileName': err.fileName,
335
'stack': err.stack
336
};
337
};
338
339
340
/**
341
* Serialize stack by including the cause chain of the exception if it exists.
342
*
343
*
344
* @param {*} e an exception that may have a cause
345
* @param {!Object=} seen set of cause that have already been serialized
346
* @return {string}
347
* @private
348
* @suppress {missingProperties} properties not defined on cause and e
349
*/
350
goog.debug.serializeErrorStack_ = function(e, seen) {
351
'use strict';
352
if (!seen) {
353
seen = {};
354
}
355
seen[goog.debug.serializeErrorAsKey_(e)] = true;
356
357
var stack = e['stack'] || '';
358
359
// Add cause if exists.
360
var cause = e.cause;
361
if (cause && !seen[goog.debug.serializeErrorAsKey_(cause)]) {
362
stack += '\nCaused by: ';
363
// Some browsers like Chrome add the error message as the first frame of the
364
// stack, In this case we don't need to add it. Note: we don't use
365
// String.startsWith method because it might have to be polyfilled.
366
if (!cause.stack || cause.stack.indexOf(cause.toString()) != 0) {
367
stack += (typeof cause === 'string') ? cause : cause.message + '\n';
368
}
369
stack += goog.debug.serializeErrorStack_(cause, seen);
370
}
371
372
return stack;
373
};
374
375
/**
376
* Serialize an error to a string key.
377
* @param {*} e an exception
378
* @return {string}
379
* @private
380
*/
381
goog.debug.serializeErrorAsKey_ = function(e) {
382
'use strict';
383
var keyPrefix = '';
384
385
if (typeof e.toString === 'function') {
386
keyPrefix = '' + e;
387
}
388
389
return keyPrefix + e['stack'];
390
};
391
392
393
/**
394
* Converts an object to an Error using the object's toString if it's not
395
* already an Error, adds a stacktrace if there isn't one, and optionally adds
396
* an extra message.
397
* @param {*} err The original thrown error, object, or string.
398
* @param {string=} opt_message optional additional message to add to the
399
* error.
400
* @return {!Error} If err is an Error, it is enhanced and returned. Otherwise,
401
* it is converted to an Error which is enhanced and returned.
402
*/
403
goog.debug.enhanceError = function(err, opt_message) {
404
'use strict';
405
var error;
406
if (!(err instanceof Error)) {
407
error = Error(err);
408
if (Error.captureStackTrace) {
409
// Trim this function off the call stack, if we can.
410
Error.captureStackTrace(error, goog.debug.enhanceError);
411
}
412
} else {
413
error = err;
414
}
415
416
if (!error.stack) {
417
error.stack = goog.debug.getStacktrace(goog.debug.enhanceError);
418
}
419
if (opt_message) {
420
// find the first unoccupied 'messageX' property
421
var x = 0;
422
while (error['message' + x]) {
423
++x;
424
}
425
error['message' + x] = String(opt_message);
426
}
427
return error;
428
};
429
430
431
/**
432
* Converts an object to an Error using the object's toString if it's not
433
* already an Error, adds a stacktrace if there isn't one, and optionally adds
434
* context to the Error, which is reported by the closure error reporter.
435
* @param {*} err The original thrown error, object, or string.
436
* @param {!Object<string, string>=} opt_context Key-value context to add to the
437
* Error.
438
* @return {!Error} If err is an Error, it is enhanced and returned. Otherwise,
439
* it is converted to an Error which is enhanced and returned.
440
*/
441
goog.debug.enhanceErrorWithContext = function(err, opt_context) {
442
'use strict';
443
var error = goog.debug.enhanceError(err);
444
if (opt_context) {
445
for (var key in opt_context) {
446
goog.debug.errorcontext.addErrorContext(error, key, opt_context[key]);
447
}
448
}
449
return error;
450
};
451
452
453
/**
454
* Gets the current stack trace. Simple and iterative - doesn't worry about
455
* catching circular references or getting the args.
456
* @param {number=} opt_depth Optional maximum depth to trace back to.
457
* @return {string} A string with the function names of all functions in the
458
* stack, separated by \n.
459
* @suppress {es5Strict}
460
*/
461
goog.debug.getStacktraceSimple = function(opt_depth) {
462
'use strict';
463
if (!goog.debug.FORCE_SLOPPY_STACKS) {
464
var stack = goog.debug.getNativeStackTrace_(goog.debug.getStacktraceSimple);
465
if (stack) {
466
return stack;
467
}
468
// NOTE: browsers that have strict mode support also have native "stack"
469
// properties. Fall-through for legacy browser support.
470
}
471
472
var sb = [];
473
var fn = arguments.callee.caller;
474
var depth = 0;
475
476
while (fn && (!opt_depth || depth < opt_depth)) {
477
sb.push(goog.debug.getFunctionName(fn));
478
sb.push('()\n');
479
480
try {
481
fn = fn.caller;
482
} catch (e) {
483
sb.push('[exception trying to get caller]\n');
484
break;
485
}
486
depth++;
487
if (depth >= goog.debug.MAX_STACK_DEPTH) {
488
sb.push('[...long stack...]');
489
break;
490
}
491
}
492
if (opt_depth && depth >= opt_depth) {
493
sb.push('[...reached max depth limit...]');
494
} else {
495
sb.push('[end]');
496
}
497
498
return sb.join('');
499
};
500
501
502
/**
503
* Max length of stack to try and output
504
* @type {number}
505
*/
506
goog.debug.MAX_STACK_DEPTH = 50;
507
508
509
/**
510
* @param {Function} fn The function to start getting the trace from.
511
* @return {?string}
512
* @private
513
*/
514
goog.debug.getNativeStackTrace_ = function(fn) {
515
'use strict';
516
var tempErr = new Error();
517
if (Error.captureStackTrace) {
518
Error.captureStackTrace(tempErr, fn);
519
return String(tempErr.stack);
520
} else {
521
// IE10, only adds stack traces when an exception is thrown.
522
try {
523
throw tempErr;
524
} catch (e) {
525
tempErr = e;
526
}
527
var stack = tempErr.stack;
528
if (stack) {
529
return String(stack);
530
}
531
}
532
return null;
533
};
534
535
536
/**
537
* Gets the current stack trace, either starting from the caller or starting
538
* from a specified function that's currently on the call stack.
539
* @param {?Function=} fn If provided, when collecting the stack trace all
540
* frames above the topmost call to this function, including that call,
541
* will be left out of the stack trace.
542
* @return {string} Stack trace.
543
* @suppress {es5Strict}
544
*/
545
goog.debug.getStacktrace = function(fn) {
546
'use strict';
547
var stack;
548
if (!goog.debug.FORCE_SLOPPY_STACKS) {
549
// Try to get the stack trace from the environment if it is available.
550
var contextFn = fn || goog.debug.getStacktrace;
551
stack = goog.debug.getNativeStackTrace_(contextFn);
552
}
553
if (!stack) {
554
// NOTE: browsers that have strict mode support also have native "stack"
555
// properties. This function will throw in strict mode.
556
stack = goog.debug.getStacktraceHelper_(fn || arguments.callee.caller, []);
557
}
558
return stack;
559
};
560
561
562
/**
563
* Private helper for getStacktrace().
564
* @param {?Function} fn If provided, when collecting the stack trace all
565
* frames above the topmost call to this function, including that call,
566
* will be left out of the stack trace.
567
* @param {Array<!Function>} visited List of functions visited so far.
568
* @return {string} Stack trace starting from function fn.
569
* @suppress {es5Strict}
570
* @private
571
*/
572
goog.debug.getStacktraceHelper_ = function(fn, visited) {
573
'use strict';
574
var sb = [];
575
576
// Circular reference, certain functions like bind seem to cause a recursive
577
// loop so we need to catch circular references
578
if (goog.array.contains(visited, fn)) {
579
sb.push('[...circular reference...]');
580
581
// Traverse the call stack until function not found or max depth is reached
582
} else if (fn && visited.length < goog.debug.MAX_STACK_DEPTH) {
583
sb.push(goog.debug.getFunctionName(fn) + '(');
584
var args = fn.arguments;
585
// Args may be null for some special functions such as host objects or eval.
586
for (var i = 0; args && i < args.length; i++) {
587
if (i > 0) {
588
sb.push(', ');
589
}
590
var argDesc;
591
var arg = args[i];
592
switch (typeof arg) {
593
case 'object':
594
argDesc = arg ? 'object' : 'null';
595
break;
596
597
case 'string':
598
argDesc = arg;
599
break;
600
601
case 'number':
602
argDesc = String(arg);
603
break;
604
605
case 'boolean':
606
argDesc = arg ? 'true' : 'false';
607
break;
608
609
case 'function':
610
argDesc = goog.debug.getFunctionName(arg);
611
argDesc = argDesc ? argDesc : '[fn]';
612
break;
613
614
case 'undefined':
615
default:
616
argDesc = typeof arg;
617
break;
618
}
619
620
if (argDesc.length > 40) {
621
argDesc = argDesc.slice(0, 40) + '...';
622
}
623
sb.push(argDesc);
624
}
625
visited.push(fn);
626
sb.push(')\n');
627
628
try {
629
sb.push(goog.debug.getStacktraceHelper_(fn.caller, visited));
630
} catch (e) {
631
sb.push('[exception trying to get caller]\n');
632
}
633
634
} else if (fn) {
635
sb.push('[...long stack...]');
636
} else {
637
sb.push('[end]');
638
}
639
return sb.join('');
640
};
641
642
643
/**
644
* Gets a function name
645
* @param {Function} fn Function to get name of.
646
* @return {string} Function's name.
647
*/
648
goog.debug.getFunctionName = function(fn) {
649
'use strict';
650
if (goog.debug.fnNameCache_[fn]) {
651
return goog.debug.fnNameCache_[fn];
652
}
653
654
// Heuristically determine function name based on code.
655
var functionSource = String(fn);
656
if (!goog.debug.fnNameCache_[functionSource]) {
657
var matches = /function\s+([^\(]+)/m.exec(functionSource);
658
if (matches) {
659
var method = matches[1];
660
goog.debug.fnNameCache_[functionSource] = method;
661
} else {
662
goog.debug.fnNameCache_[functionSource] = '[Anonymous]';
663
}
664
}
665
666
return goog.debug.fnNameCache_[functionSource];
667
};
668
669
670
/**
671
* Makes whitespace visible by replacing it with printable characters.
672
* This is useful in finding diffrences between the expected and the actual
673
* output strings of a testcase.
674
* @param {string} string whose whitespace needs to be made visible.
675
* @return {string} string whose whitespace is made visible.
676
*/
677
goog.debug.makeWhitespaceVisible = function(string) {
678
'use strict';
679
return string.replace(/ /g, '[_]')
680
.replace(/\f/g, '[f]')
681
.replace(/\n/g, '[n]\n')
682
.replace(/\r/g, '[r]')
683
.replace(/\t/g, '[t]');
684
};
685
686
687
/**
688
* Returns the type of a value. If a constructor is passed, and a suitable
689
* string cannot be found, 'unknown type name' will be returned.
690
*
691
* <p>Forked rather than moved from {@link goog.asserts.getType_}
692
* to avoid adding a dependency to goog.asserts.
693
* @param {*} value A constructor, object, or primitive.
694
* @return {string} The best display name for the value, or 'unknown type name'.
695
*/
696
goog.debug.runtimeType = function(value) {
697
'use strict';
698
if (value instanceof Function) {
699
return value.displayName || value.name || 'unknown type name';
700
} else if (value instanceof Object) {
701
return /** @type {string} */ (value.constructor.displayName) ||
702
value.constructor.name || Object.prototype.toString.call(value);
703
} else {
704
return value === null ? 'null' : typeof value;
705
}
706
};
707
708
709
/**
710
* Hash map for storing function names that have already been looked up.
711
* @type {Object}
712
* @private
713
*/
714
goog.debug.fnNameCache_ = {};
715
716
717
/**
718
* Private internal function to support goog.debug.freeze.
719
* @param {T} arg
720
* @return {T}
721
* @template T
722
* @private
723
*/
724
goog.debug.freezeInternal_ = goog.DEBUG && Object.freeze || function(arg) {
725
'use strict';
726
return arg;
727
};
728
729
730
/**
731
* Freezes the given object, but only in debug mode (and in browsers that
732
* support it). Note that this is a shallow freeze, so for deeply nested
733
* objects it must be called at every level to ensure deep immutability.
734
* @param {T} arg
735
* @return {T}
736
* @template T
737
*/
738
goog.debug.freeze = function(arg) {
739
'use strict';
740
// NOTE: this compiles to nothing, but hides the possible side effect of
741
// freezeInternal_ from the compiler so that the entire call can be
742
// removed if the result is not used.
743
return {
744
valueOf: function() {
745
'use strict';
746
return goog.debug.freezeInternal_(arg);
747
}
748
}.valueOf();
749
};
750
751