Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
seleniumhq
GitHub Repository: seleniumhq/selenium
Path: blob/trunk/third_party/closure/goog/debug/debug.js
2868 views
1
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS-IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
/**
16
* @fileoverview Logging and debugging utilities.
17
*
18
* @see ../demos/debug.html
19
*/
20
21
goog.provide('goog.debug');
22
23
goog.require('goog.array');
24
goog.require('goog.userAgent');
25
26
27
/** @define {boolean} Whether logging should be enabled. */
28
goog.define('goog.debug.LOGGING_ENABLED', goog.DEBUG);
29
30
31
/** @define {boolean} Whether to force "sloppy" stack building. */
32
goog.define('goog.debug.FORCE_SLOPPY_STACKS', false);
33
34
35
/**
36
* Catches onerror events fired by windows and similar objects.
37
* @param {function(Object)} logFunc The function to call with the error
38
* information.
39
* @param {boolean=} opt_cancel Whether to stop the error from reaching the
40
* browser.
41
* @param {Object=} opt_target Object that fires onerror events.
42
*/
43
goog.debug.catchErrors = function(logFunc, opt_cancel, opt_target) {
44
var target = opt_target || goog.global;
45
var oldErrorHandler = target.onerror;
46
var retVal = !!opt_cancel;
47
48
// Chrome interprets onerror return value backwards (http://crbug.com/92062)
49
// until it was fixed in webkit revision r94061 (Webkit 535.3). This
50
// workaround still needs to be skipped in Safari after the webkit change
51
// gets pushed out in Safari.
52
// See https://bugs.webkit.org/show_bug.cgi?id=67119
53
if (goog.userAgent.WEBKIT && !goog.userAgent.isVersionOrHigher('535.3')) {
54
retVal = !retVal;
55
}
56
57
/**
58
* New onerror handler for this target. This onerror handler follows the spec
59
* according to
60
* http://www.whatwg.org/specs/web-apps/current-work/#runtime-script-errors
61
* The spec was changed in August 2013 to support receiving column information
62
* and an error object for all scripts on the same origin or cross origin
63
* scripts with the proper headers. See
64
* https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror
65
*
66
* @param {string} message The error message. For cross-origin errors, this
67
* will be scrubbed to just "Script error.". For new browsers that have
68
* updated to follow the latest spec, errors that come from origins that
69
* have proper cross origin headers will not be scrubbed.
70
* @param {string} url The URL of the script that caused the error. The URL
71
* will be scrubbed to "" for cross origin scripts unless the script has
72
* proper cross origin headers and the browser has updated to the latest
73
* spec.
74
* @param {number} line The line number in the script that the error
75
* occurred on.
76
* @param {number=} opt_col The optional column number that the error
77
* occurred on. Only browsers that have updated to the latest spec will
78
* include this.
79
* @param {Error=} opt_error The optional actual error object for this
80
* error that should include the stack. Only browsers that have updated
81
* to the latest spec will inlude this parameter.
82
* @return {boolean} Whether to prevent the error from reaching the browser.
83
*/
84
target.onerror = function(message, url, line, opt_col, opt_error) {
85
if (oldErrorHandler) {
86
oldErrorHandler(message, url, line, opt_col, opt_error);
87
}
88
logFunc({
89
message: message,
90
fileName: url,
91
line: 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 {@code obj}.
106
*/
107
goog.debug.expose = function(obj, opt_showFn) {
108
if (typeof obj == 'undefined') {
109
return 'undefined';
110
}
111
if (obj == null) {
112
return 'NULL';
113
}
114
var str = [];
115
116
for (var x in obj) {
117
if (!opt_showFn && goog.isFunction(obj[x])) {
118
continue;
119
}
120
var s = x + ' = ';
121
122
try {
123
s += obj[x];
124
} catch (e) {
125
s += '*** ' + e + ' ***';
126
}
127
str.push(s);
128
}
129
return str.join('\n');
130
};
131
132
133
/**
134
* Creates a string representing a given primitive or object, and for an
135
* object, all its properties and nested objects. NOTE: The output will include
136
* Uids on all objects that were exposed. Any added Uids will be removed before
137
* returning.
138
* @param {*} obj Object to expose.
139
* @param {boolean=} opt_showFn Also show properties that are functions (by
140
* default, functions are omitted).
141
* @return {string} A string representation of {@code obj}.
142
*/
143
goog.debug.deepExpose = function(obj, opt_showFn) {
144
var str = [];
145
146
// Track any objects where deepExpose added a Uid, so they can be cleaned up
147
// before return. We do this globally, rather than only on ancestors so that
148
// if the same object appears in the output, you can see it.
149
var uidsToCleanup = [];
150
var ancestorUids = {};
151
152
var helper = function(obj, space) {
153
var nestspace = space + ' ';
154
155
var indentMultiline = function(str) {
156
return str.replace(/\n/g, '\n' + space);
157
};
158
159
160
try {
161
if (!goog.isDef(obj)) {
162
str.push('undefined');
163
} else if (goog.isNull(obj)) {
164
str.push('NULL');
165
} else if (goog.isString(obj)) {
166
str.push('"' + indentMultiline(obj) + '"');
167
} else if (goog.isFunction(obj)) {
168
str.push(indentMultiline(String(obj)));
169
} else if (goog.isObject(obj)) {
170
// Add a Uid if needed. The struct calls implicitly adds them.
171
if (!goog.hasUid(obj)) {
172
uidsToCleanup.push(obj);
173
}
174
var uid = goog.getUid(obj);
175
if (ancestorUids[uid]) {
176
str.push('*** reference loop detected (id=' + uid + ') ***');
177
} else {
178
ancestorUids[uid] = true;
179
str.push('{');
180
for (var x in obj) {
181
if (!opt_showFn && goog.isFunction(obj[x])) {
182
continue;
183
}
184
str.push('\n');
185
str.push(nestspace);
186
str.push(x + ' = ');
187
helper(obj[x], nestspace);
188
}
189
str.push('\n' + space + '}');
190
delete ancestorUids[uid];
191
}
192
} else {
193
str.push(obj);
194
}
195
} catch (e) {
196
str.push('*** ' + e + ' ***');
197
}
198
};
199
200
helper(obj, '');
201
202
// Cleanup any Uids that were added by the deepExpose.
203
for (var i = 0; i < uidsToCleanup.length; i++) {
204
goog.removeUid(uidsToCleanup[i]);
205
}
206
207
return str.join('');
208
};
209
210
211
/**
212
* Recursively outputs a nested array as a string.
213
* @param {Array<?>} arr The array.
214
* @return {string} String representing nested array.
215
*/
216
goog.debug.exposeArray = function(arr) {
217
var str = [];
218
for (var i = 0; i < arr.length; i++) {
219
if (goog.isArray(arr[i])) {
220
str.push(goog.debug.exposeArray(arr[i]));
221
} else {
222
str.push(arr[i]);
223
}
224
}
225
return '[ ' + str.join(', ') + ' ]';
226
};
227
228
229
/**
230
* Normalizes the error/exception object between browsers.
231
* @param {*} err Raw error object.
232
* @return {!{
233
* message: (?|undefined),
234
* name: (?|undefined),
235
* lineNumber: (?|undefined),
236
* fileName: (?|undefined),
237
* stack: (?|undefined)
238
* }} Normalized error object.
239
*/
240
goog.debug.normalizeErrorObject = function(err) {
241
var href = goog.getObjectByName('window.location.href');
242
if (goog.isString(err)) {
243
return {
244
'message': err,
245
'name': 'Unknown error',
246
'lineNumber': 'Not available',
247
'fileName': href,
248
'stack': 'Not available'
249
};
250
}
251
252
var lineNumber, fileName;
253
var threwError = false;
254
255
try {
256
lineNumber = err.lineNumber || err.line || 'Not available';
257
} catch (e) {
258
// Firefox 2 sometimes throws an error when accessing 'lineNumber':
259
// Message: Permission denied to get property UnnamedClass.lineNumber
260
lineNumber = 'Not available';
261
threwError = true;
262
}
263
264
try {
265
fileName = err.fileName || err.filename || err.sourceURL ||
266
// $googDebugFname may be set before a call to eval to set the filename
267
// that the eval is supposed to present.
268
goog.global['$googDebugFname'] || href;
269
} catch (e) {
270
// Firefox 2 may also throw an error when accessing 'filename'.
271
fileName = 'Not available';
272
threwError = true;
273
}
274
275
// The IE Error object contains only the name and the message.
276
// The Safari Error object uses the line and sourceURL fields.
277
if (threwError || !err.lineNumber || !err.fileName || !err.stack ||
278
!err.message || !err.name) {
279
return {
280
'message': err.message || 'Not available',
281
'name': err.name || 'UnknownError',
282
'lineNumber': lineNumber,
283
'fileName': fileName,
284
'stack': err.stack || 'Not available'
285
};
286
}
287
288
// Standards error object
289
// Typed !Object. Should be a subtype of the return type, but it's not.
290
return /** @type {?} */ (err);
291
};
292
293
294
/**
295
* Converts an object to an Error using the object's toString if it's not
296
* already an Error, adds a stacktrace if there isn't one, and optionally adds
297
* an extra message.
298
* @param {*} err The original thrown error, object, or string.
299
* @param {string=} opt_message optional additional message to add to the
300
* error.
301
* @return {!Error} If err is an Error, it is enhanced and returned. Otherwise,
302
* it is converted to an Error which is enhanced and returned.
303
*/
304
goog.debug.enhanceError = function(err, opt_message) {
305
var error;
306
if (!(err instanceof Error)) {
307
error = Error(err);
308
if (Error.captureStackTrace) {
309
// Trim this function off the call stack, if we can.
310
Error.captureStackTrace(error, goog.debug.enhanceError);
311
}
312
} else {
313
error = err;
314
}
315
316
if (!error.stack) {
317
error.stack = goog.debug.getStacktrace(goog.debug.enhanceError);
318
}
319
if (opt_message) {
320
// find the first unoccupied 'messageX' property
321
var x = 0;
322
while (error['message' + x]) {
323
++x;
324
}
325
error['message' + x] = String(opt_message);
326
}
327
return error;
328
};
329
330
331
/**
332
* Gets the current stack trace. Simple and iterative - doesn't worry about
333
* catching circular references or getting the args.
334
* @param {number=} opt_depth Optional maximum depth to trace back to.
335
* @return {string} A string with the function names of all functions in the
336
* stack, separated by \n.
337
* @suppress {es5Strict}
338
*/
339
goog.debug.getStacktraceSimple = function(opt_depth) {
340
if (!goog.debug.FORCE_SLOPPY_STACKS) {
341
var stack = goog.debug.getNativeStackTrace_(goog.debug.getStacktraceSimple);
342
if (stack) {
343
return stack;
344
}
345
// NOTE: browsers that have strict mode support also have native "stack"
346
// properties. Fall-through for legacy browser support.
347
}
348
349
var sb = [];
350
var fn = arguments.callee.caller;
351
var depth = 0;
352
353
while (fn && (!opt_depth || depth < opt_depth)) {
354
sb.push(goog.debug.getFunctionName(fn));
355
sb.push('()\n');
356
357
try {
358
fn = fn.caller;
359
} catch (e) {
360
sb.push('[exception trying to get caller]\n');
361
break;
362
}
363
depth++;
364
if (depth >= goog.debug.MAX_STACK_DEPTH) {
365
sb.push('[...long stack...]');
366
break;
367
}
368
}
369
if (opt_depth && depth >= opt_depth) {
370
sb.push('[...reached max depth limit...]');
371
} else {
372
sb.push('[end]');
373
}
374
375
return sb.join('');
376
};
377
378
379
/**
380
* Max length of stack to try and output
381
* @type {number}
382
*/
383
goog.debug.MAX_STACK_DEPTH = 50;
384
385
386
/**
387
* @param {Function} fn The function to start getting the trace from.
388
* @return {?string}
389
* @private
390
*/
391
goog.debug.getNativeStackTrace_ = function(fn) {
392
var tempErr = new Error();
393
if (Error.captureStackTrace) {
394
Error.captureStackTrace(tempErr, fn);
395
return String(tempErr.stack);
396
} else {
397
// IE10, only adds stack traces when an exception is thrown.
398
try {
399
throw tempErr;
400
} catch (e) {
401
tempErr = e;
402
}
403
var stack = tempErr.stack;
404
if (stack) {
405
return String(stack);
406
}
407
}
408
return null;
409
};
410
411
412
/**
413
* Gets the current stack trace, either starting from the caller or starting
414
* from a specified function that's currently on the call stack.
415
* @param {?Function=} fn If provided, when collecting the stack trace all
416
* frames above the topmost call to this function, including that call,
417
* will be left out of the stack trace.
418
* @return {string} Stack trace.
419
* @suppress {es5Strict}
420
*/
421
goog.debug.getStacktrace = function(fn) {
422
var stack;
423
if (!goog.debug.FORCE_SLOPPY_STACKS) {
424
// Try to get the stack trace from the environment if it is available.
425
var contextFn = fn || goog.debug.getStacktrace;
426
stack = goog.debug.getNativeStackTrace_(contextFn);
427
}
428
if (!stack) {
429
// NOTE: browsers that have strict mode support also have native "stack"
430
// properties. This function will throw in strict mode.
431
stack = goog.debug.getStacktraceHelper_(fn || arguments.callee.caller, []);
432
}
433
return stack;
434
};
435
436
437
/**
438
* Private helper for getStacktrace().
439
* @param {?Function} fn If provided, when collecting the stack trace all
440
* frames above the topmost call to this function, including that call,
441
* will be left out of the stack trace.
442
* @param {Array<!Function>} visited List of functions visited so far.
443
* @return {string} Stack trace starting from function fn.
444
* @suppress {es5Strict}
445
* @private
446
*/
447
goog.debug.getStacktraceHelper_ = function(fn, visited) {
448
var sb = [];
449
450
// Circular reference, certain functions like bind seem to cause a recursive
451
// loop so we need to catch circular references
452
if (goog.array.contains(visited, fn)) {
453
sb.push('[...circular reference...]');
454
455
// Traverse the call stack until function not found or max depth is reached
456
} else if (fn && visited.length < goog.debug.MAX_STACK_DEPTH) {
457
sb.push(goog.debug.getFunctionName(fn) + '(');
458
var args = fn.arguments;
459
// Args may be null for some special functions such as host objects or eval.
460
for (var i = 0; args && i < args.length; i++) {
461
if (i > 0) {
462
sb.push(', ');
463
}
464
var argDesc;
465
var arg = args[i];
466
switch (typeof arg) {
467
case 'object':
468
argDesc = arg ? 'object' : 'null';
469
break;
470
471
case 'string':
472
argDesc = arg;
473
break;
474
475
case 'number':
476
argDesc = String(arg);
477
break;
478
479
case 'boolean':
480
argDesc = arg ? 'true' : 'false';
481
break;
482
483
case 'function':
484
argDesc = goog.debug.getFunctionName(arg);
485
argDesc = argDesc ? argDesc : '[fn]';
486
break;
487
488
case 'undefined':
489
default:
490
argDesc = typeof arg;
491
break;
492
}
493
494
if (argDesc.length > 40) {
495
argDesc = argDesc.substr(0, 40) + '...';
496
}
497
sb.push(argDesc);
498
}
499
visited.push(fn);
500
sb.push(')\n');
501
502
try {
503
sb.push(goog.debug.getStacktraceHelper_(fn.caller, visited));
504
} catch (e) {
505
sb.push('[exception trying to get caller]\n');
506
}
507
508
} else if (fn) {
509
sb.push('[...long stack...]');
510
} else {
511
sb.push('[end]');
512
}
513
return sb.join('');
514
};
515
516
517
/**
518
* Set a custom function name resolver.
519
* @param {function(Function): string} resolver Resolves functions to their
520
* names.
521
*/
522
goog.debug.setFunctionResolver = function(resolver) {
523
goog.debug.fnNameResolver_ = resolver;
524
};
525
526
527
/**
528
* Gets a function name
529
* @param {Function} fn Function to get name of.
530
* @return {string} Function's name.
531
*/
532
goog.debug.getFunctionName = function(fn) {
533
if (goog.debug.fnNameCache_[fn]) {
534
return goog.debug.fnNameCache_[fn];
535
}
536
if (goog.debug.fnNameResolver_) {
537
var name = goog.debug.fnNameResolver_(fn);
538
if (name) {
539
goog.debug.fnNameCache_[fn] = name;
540
return name;
541
}
542
}
543
544
// Heuristically determine function name based on code.
545
var functionSource = String(fn);
546
if (!goog.debug.fnNameCache_[functionSource]) {
547
var matches = /function ([^\(]+)/.exec(functionSource);
548
if (matches) {
549
var method = matches[1];
550
goog.debug.fnNameCache_[functionSource] = method;
551
} else {
552
goog.debug.fnNameCache_[functionSource] = '[Anonymous]';
553
}
554
}
555
556
return goog.debug.fnNameCache_[functionSource];
557
};
558
559
560
/**
561
* Makes whitespace visible by replacing it with printable characters.
562
* This is useful in finding diffrences between the expected and the actual
563
* output strings of a testcase.
564
* @param {string} string whose whitespace needs to be made visible.
565
* @return {string} string whose whitespace is made visible.
566
*/
567
goog.debug.makeWhitespaceVisible = function(string) {
568
return string.replace(/ /g, '[_]')
569
.replace(/\f/g, '[f]')
570
.replace(/\n/g, '[n]\n')
571
.replace(/\r/g, '[r]')
572
.replace(/\t/g, '[t]');
573
};
574
575
576
/**
577
* Returns the type of a value. If a constructor is passed, and a suitable
578
* string cannot be found, 'unknown type name' will be returned.
579
*
580
* <p>Forked rather than moved from {@link goog.asserts.getType_}
581
* to avoid adding a dependency to goog.asserts.
582
* @param {*} value A constructor, object, or primitive.
583
* @return {string} The best display name for the value, or 'unknown type name'.
584
*/
585
goog.debug.runtimeType = function(value) {
586
if (value instanceof Function) {
587
return value.displayName || value.name || 'unknown type name';
588
} else if (value instanceof Object) {
589
return value.constructor.displayName || value.constructor.name ||
590
Object.prototype.toString.call(value);
591
} else {
592
return value === null ? 'null' : typeof value;
593
}
594
};
595
596
597
/**
598
* Hash map for storing function names that have already been looked up.
599
* @type {Object}
600
* @private
601
*/
602
goog.debug.fnNameCache_ = {};
603
604
605
/**
606
* Resolves functions to their names. Resolved function names will be cached.
607
* @type {function(Function):string}
608
* @private
609
*/
610
goog.debug.fnNameResolver_;
611
612