whoopsie 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +144 -0
  4. data/Rakefile +29 -0
  5. data/app/assets/javascripts/whoopsie.js +2 -0
  6. data/app/assets/javascripts/whoopsie/tracekit-config.js +49 -0
  7. data/app/assets/javascripts/whoopsie/tracekit.js +1166 -0
  8. data/app/controllers/whoopsie/application_controller.rb +4 -0
  9. data/app/controllers/whoopsie/errors_controller.rb +50 -0
  10. data/app/helpers/whoopsie/whoopsie_helper.rb +18 -0
  11. data/config/routes.rb +5 -0
  12. data/lib/whoopsie.rb +20 -0
  13. data/lib/whoopsie/engine.rb +9 -0
  14. data/lib/whoopsie/railtie.rb +39 -0
  15. data/lib/whoopsie/version.rb +3 -0
  16. data/spec/controllers/whoopsie/errors_controller_spec.rb +71 -0
  17. data/spec/dummy/README.rdoc +28 -0
  18. data/spec/dummy/Rakefile +6 -0
  19. data/spec/dummy/app/assets/javascripts/application.js +3 -0
  20. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  21. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  22. data/spec/dummy/app/controllers/welcome_controller.rb +4 -0
  23. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  24. data/spec/dummy/app/views/layouts/application.html.erb +17 -0
  25. data/spec/dummy/app/views/welcome/show.html.erb +3 -0
  26. data/spec/dummy/bin/bundle +3 -0
  27. data/spec/dummy/bin/rails +4 -0
  28. data/spec/dummy/bin/rake +4 -0
  29. data/spec/dummy/bin/setup +29 -0
  30. data/spec/dummy/config.ru +4 -0
  31. data/spec/dummy/config/application.rb +38 -0
  32. data/spec/dummy/config/boot.rb +5 -0
  33. data/spec/dummy/config/database.yml +22 -0
  34. data/spec/dummy/config/environment.rb +5 -0
  35. data/spec/dummy/config/environments/development.rb +38 -0
  36. data/spec/dummy/config/environments/production.rb +76 -0
  37. data/spec/dummy/config/environments/test.rb +42 -0
  38. data/spec/dummy/config/initializers/assets.rb +11 -0
  39. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  40. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  41. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  42. data/spec/dummy/config/initializers/inflections.rb +16 -0
  43. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  44. data/spec/dummy/config/initializers/session_store.rb +3 -0
  45. data/spec/dummy/config/initializers/wrap_parameters.rb +9 -0
  46. data/spec/dummy/config/locales/en.yml +23 -0
  47. data/spec/dummy/config/routes.rb +3 -0
  48. data/spec/dummy/config/secrets.yml +22 -0
  49. data/spec/dummy/db/test.sqlite3 +0 -0
  50. data/spec/dummy/public/404.html +67 -0
  51. data/spec/dummy/public/422.html +67 -0
  52. data/spec/dummy/public/500.html +66 -0
  53. data/spec/dummy/public/favicon.ico +0 -0
  54. data/spec/examples.txt +11 -0
  55. data/spec/lib/whoopsie_spec.rb +41 -0
  56. data/spec/rails_helper.rb +57 -0
  57. data/spec/spec_helper.rb +92 -0
  58. metadata +262 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a9e4b626f98593b5c90af618b15fdd90e390e8ca
4
+ data.tar.gz: 0d906d6b2773ea4e47ede525b90cc8f152d291a0
5
+ SHA512:
6
+ metadata.gz: 909c8dbba375b3d9d0e4bb5e49772b4eef61bd63e78a1e0ab72a4d68cb5c1d68d70ee5753669465c53b3af0d02b97506bdc66328b12b33e2159933a97f269f58
7
+ data.tar.gz: 1cb9c339cc154eb36a6cf216ae8418a95479b918086f37e54653689849fe5543e0b1a0f622d2c7116cb48abdbce63d75d86d148731d8b44d023eb1a8bfdd6d4c
@@ -0,0 +1,20 @@
1
+ Copyright 2015 Wojtek Kruszewski
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,144 @@
1
+ # Whoopsie
2
+
3
+ **Version: 0.0.2**
4
+
5
+ A wrapper for ExceptionNotifier and TraceKit, especially suited for Rails apps.
6
+
7
+ This ***Rails engine*** was originally written by [Wojtek Kruszewski](https://github.com/OXOS/whoopsie).
8
+
9
+ RSpec tests, minimal documentation (below), and some cleanup by [Midwire](https://github.com/midwire)
10
+
11
+ Whoopsie wraps [ExceptionNotification](https://github.com/midwire/exception_notification) to handle notifications for JavaScript errors as well as back-end errors.
12
+
13
+ ## Installation
14
+
15
+ Add this line to your application's Gemfile:
16
+
17
+ ```ruby
18
+ gem 'whoopsie'
19
+ ```
20
+
21
+ And then execute:
22
+
23
+ $ bundle
24
+
25
+ Or install it yourself as:
26
+
27
+ $ gem install whoopsie
28
+
29
+ ## Usage
30
+
31
+ ### Rails Usage
32
+
33
+ #### Configuration
34
+
35
+ Enable it in `config/application.rb`:
36
+
37
+ config.whoopsie.enable = true
38
+ config.whoopsie.email_prefix = '[ERROR] ' # optional
39
+ config.whoopsie.recipients = ['me@example.com', 'you@example.com']
40
+ config.whoopsie.sender = 'notifications@example.com'
41
+ config.ignored_exceptions += %w{ActionView::TemplateError CustomError} # optional
42
+
43
+ ...or in any initializer (example: `config/initializers/whoopsie.rb`):
44
+
45
+ Rails.application.config.whoopsie.enable = true
46
+ Rails.application.config.whoopsie.email_prefix = '[ERROR] ' # optional
47
+ Rails.application.config.whoopsie.recipients = ['me@example.com', 'you@example.com']
48
+ Rails.application.config.whoopsie.sender = 'notifications@example.com'
49
+ Rails.application.config.ignored_exceptions += %w{ActionView::TemplateError CustomError} # optional
50
+
51
+ You can ignore certain exceptions if you don't want to be notified for them.
52
+
53
+ config.ignored_exceptions += %w{ActionView::TemplateError CustomError}
54
+
55
+ ActiveRecord::RecordNotFound, AbstractController::ActionNotFound and ActionController::RoutingError are ignored by default.
56
+
57
+ Add a condition to determine when an exception must be ignored or not. The `ignore_if` method can be invoked multiple times to add extra conditions.
58
+
59
+ config.ignore_if do |exception, options|
60
+ ! Rails.application.config.whoopsie.enable
61
+ end
62
+
63
+ ##### Additional Notifiers
64
+
65
+ The Email notifier is configured automatically when you configure Whoopsie as above.
66
+
67
+ Add additional notifiers just like you normally would for [ExceptionNotification](https://github.com/midwire/exception_notification). For example, to add a Slack channel notification:
68
+
69
+ Rails.application.config.middleware.use(
70
+ ExceptionNotification::Rack,
71
+ :slack => {
72
+ :webhook_url => 'YOUR_SLACK_WEBHOOK_URL_GOES_HERE',
73
+ :channel => '#my_app_channel',
74
+ :additional_parameters => {
75
+ :icon_url => 'https://myapp.com/assets/logo-square.png',
76
+ :mrkdwn => true
77
+ }
78
+ }
79
+ )
80
+
81
+ #### Usage
82
+
83
+ Exceptions will be automatically caught and notifications sent using the configured notifiers. You can also handle exceptions explicitly:
84
+
85
+ def my_method
86
+ raise 'oh noes!'
87
+ rescue SomeError => err
88
+ Whoopsie.handle_exception(
89
+ err,
90
+ data: {
91
+ id: 'whatever',
92
+ another_thing: 'something else',
93
+ errors: %w(array of things)
94
+ }
95
+ )
96
+ end
97
+
98
+ Wrap code for protection:
99
+
100
+ Whoopsie.report_and_swallow do
101
+ # Do something risky...
102
+ end
103
+
104
+ ### Javascript Usage
105
+
106
+ Require it in your JS
107
+
108
+ # assets/javascripts/application.js
109
+ //= require whoopsie
110
+
111
+ Add the config helper to your layout:
112
+
113
+ <%= whoopsie_config %>
114
+
115
+ or for HAML
116
+
117
+ = whoopsie_config
118
+
119
+ Wrap any JS that you want to be protected with TraceKit:
120
+
121
+ Whoopsie.wrap(function($){
122
+ $(document).on('click', 'button[data-dismiss-hint]', function(event){
123
+ alert("All is well.")
124
+ });
125
+ });
126
+
127
+ or wrap long methods by name:
128
+
129
+ $(document).ready(Whoopsie.wrap(myFunction));
130
+
131
+ ### Coffeescript Usage
132
+
133
+ TODO
134
+
135
+ ## Bugs
136
+
137
+ Please report any bugs or issues using [GitHub Issue Tracker](https://github.com/midwire/whoopsie/issues).
138
+
139
+ ## Contributing
140
+
141
+ Bug reports and pull requests are welcome on GitHub at https://github.com/midwire/whoopsie.
142
+
143
+ Please use feature branches and follow the Git Flow paradigm.
144
+
@@ -0,0 +1,29 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ require 'bundler/gem_tasks'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+
8
+ begin
9
+ require 'midwire_common/rake_tasks'
10
+ rescue RuntimeError
11
+ puts '>>> Could not load midwire_common/rake_tasks'
12
+ exit
13
+ end
14
+
15
+ load 'rails/tasks/statistics.rake'
16
+ Bundler::GemHelper.install_tasks
17
+
18
+ require 'rake/testtask'
19
+ Rake::TestTask.new(:test) do |t|
20
+ t.libs << 'lib'
21
+ t.libs << 'test'
22
+ t.pattern = 'test/**/*_test.rb'
23
+ t.verbose = false
24
+ end
25
+ # task default: :test
26
+
27
+ require 'rspec/core/rake_task'
28
+ RSpec::Core::RakeTask.new(:spec)
29
+ task default: :spec
@@ -0,0 +1,2 @@
1
+ //= require whoopsie/tracekit
2
+ //= require whoopsie/tracekit-config
@@ -0,0 +1,49 @@
1
+ if (typeof window.Whoopsie !== "object") {
2
+ alert("Error notifications not configured. Did you include whoopsie_helper?");
3
+ }
4
+
5
+ if (window.Whoopsie && window.Whoopsie.enabled) {
6
+ TraceKit.report.subscribe(function(errorReport) {
7
+ jQuery.ajax({
8
+ url: window.Whoopsie.client_notification_url,
9
+ type: "POST",
10
+ data: {
11
+ error_report: errorReport,
12
+ extra: Whoopsie.extra(),
13
+ }
14
+ });
15
+ return true;
16
+ });
17
+
18
+ jQuery.extend({
19
+ error: function(msg) {
20
+ var error;
21
+ error = new Error(msg);
22
+ TraceKit.report(error);
23
+ return null;
24
+ }
25
+ });
26
+ } else {
27
+ TraceKit.report.subscribe(function(errorReport) {
28
+ console.log("TraceKit report", JSON.stringify(errorReport));
29
+ });
30
+
31
+ TraceKit.wrap = function(func) {
32
+ function wrapped() {
33
+ return func.apply(this, arguments);
34
+ }
35
+ return wrapped;
36
+ };
37
+ }
38
+
39
+ TraceKit.run = function(func){
40
+ TraceKit.wrap(func).apply(this, arguments);
41
+ };
42
+
43
+ Whoopsie.wrap = TraceKit.wrap;
44
+
45
+ Whoopsie.run = function(func){
46
+ Whoopsie.wrap(func).apply(this, arguments);
47
+ };
48
+
49
+ $.ajaxSettings.converters["text script"] = TraceKit.wrap(window.eval);
@@ -0,0 +1,1166 @@
1
+ /*
2
+ TraceKit - Cross browser stack traces - github.com/csnover/TraceKit
3
+ MIT license
4
+ */
5
+
6
+ (function(window, undefined) {
7
+ if (!window) {
8
+ return;
9
+ }
10
+
11
+ var TraceKit = {};
12
+ var _oldTraceKit = window.TraceKit;
13
+
14
+ // global reference to slice
15
+ var _slice = [].slice;
16
+ var UNKNOWN_FUNCTION = '?';
17
+
18
+
19
+ /**
20
+ * _has, a better form of hasOwnProperty
21
+ * Example: _has(MainHostObject, property) === true/false
22
+ *
23
+ * @param {Object} object to check property
24
+ * @param {string} key to check
25
+ */
26
+ function _has(object, key) {
27
+ return Object.prototype.hasOwnProperty.call(object, key);
28
+ }
29
+
30
+ function _isUndefined(what) {
31
+ return typeof what === 'undefined';
32
+ }
33
+
34
+ /**
35
+ * TraceKit.noConflict: Export TraceKit out to another variable
36
+ * Example: var TK = TraceKit.noConflict()
37
+ */
38
+ TraceKit.noConflict = function noConflict() {
39
+ window.TraceKit = _oldTraceKit;
40
+ return TraceKit;
41
+ };
42
+
43
+ /**
44
+ * TraceKit.wrap: Wrap any function in a TraceKit reporter
45
+ * Example: func = TraceKit.wrap(func);
46
+ *
47
+ * @param {Function} func Function to be wrapped
48
+ * @return {Function} The wrapped func
49
+ */
50
+ TraceKit.wrap = function traceKitWrapper(func) {
51
+ function wrapped() {
52
+ try {
53
+ return func.apply(this, arguments);
54
+ } catch (e) {
55
+ TraceKit.report(e);
56
+ throw e;
57
+ }
58
+ }
59
+ return wrapped;
60
+ };
61
+
62
+ /**
63
+ * TraceKit.report: cross-browser processing of unhandled exceptions
64
+ *
65
+ * Syntax:
66
+ * TraceKit.report.subscribe(function(stackInfo) { ... })
67
+ * TraceKit.report.unsubscribe(function(stackInfo) { ... })
68
+ * TraceKit.report(exception)
69
+ * try { ...code... } catch(ex) { TraceKit.report(ex); }
70
+ *
71
+ * Supports:
72
+ * - Firefox: full stack trace with line numbers, plus column number
73
+ * on top frame; column number is not guaranteed
74
+ * - Opera: full stack trace with line and column numbers
75
+ * - Chrome: full stack trace with line and column numbers
76
+ * - Safari: line and column number for the top frame only; some frames
77
+ * may be missing, and column number is not guaranteed
78
+ * - IE: line and column number for the top frame only; some frames
79
+ * may be missing, and column number is not guaranteed
80
+ *
81
+ * In theory, TraceKit should work on all of the following versions:
82
+ * - IE5.5+ (only 8.0 tested)
83
+ * - Firefox 0.9+ (only 3.5+ tested)
84
+ * - Opera 7+ (only 10.50 tested; versions 9 and earlier may require
85
+ * Exceptions Have Stacktrace to be enabled in opera:config)
86
+ * - Safari 3+ (only 4+ tested)
87
+ * - Chrome 1+ (only 5+ tested)
88
+ * - Konqueror 3.5+ (untested)
89
+ *
90
+ * Requires TraceKit.computeStackTrace.
91
+ *
92
+ * Tries to catch all unhandled exceptions and report them to the
93
+ * subscribed handlers. Please note that TraceKit.report will rethrow the
94
+ * exception. This is REQUIRED in order to get a useful stack trace in IE.
95
+ * If the exception does not reach the top of the browser, you will only
96
+ * get a stack trace from the point where TraceKit.report was called.
97
+ *
98
+ * Handlers receive a stackInfo object as described in the
99
+ * TraceKit.computeStackTrace docs.
100
+ */
101
+ TraceKit.report = (function reportModuleWrapper() {
102
+ var handlers = [],
103
+ lastArgs = null,
104
+ lastException = null,
105
+ lastExceptionStack = null;
106
+
107
+ /**
108
+ * Add a crash handler.
109
+ * @param {Function} handler
110
+ */
111
+ function subscribe(handler) {
112
+ installGlobalHandler();
113
+ handlers.push(handler);
114
+ }
115
+
116
+ /**
117
+ * Remove a crash handler.
118
+ * @param {Function} handler
119
+ */
120
+ function unsubscribe(handler) {
121
+ for (var i = handlers.length - 1; i >= 0; --i) {
122
+ if (handlers[i] === handler) {
123
+ handlers.splice(i, 1);
124
+ }
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Dispatch stack information to all handlers.
130
+ * @param {Object.<string, *>} stack
131
+ */
132
+ function notifyHandlers(stack, isWindowError) {
133
+ var exception = null;
134
+ if (isWindowError && !TraceKit.collectWindowErrors) {
135
+ return;
136
+ }
137
+ for (var i in handlers) {
138
+ if (_has(handlers, i)) {
139
+ try {
140
+ handlers[i].apply(null, [stack].concat(_slice.call(arguments, 2)));
141
+ } catch (inner) {
142
+ exception = inner;
143
+ }
144
+ }
145
+ }
146
+
147
+ if (exception) {
148
+ throw exception;
149
+ }
150
+ }
151
+
152
+ var _oldOnerrorHandler, _onErrorHandlerInstalled;
153
+
154
+ /**
155
+ * Ensures all global unhandled exceptions are recorded.
156
+ * Supported by Gecko and IE.
157
+ * @param {string} message Error message.
158
+ * @param {string} url URL of script that generated the exception.
159
+ * @param {(number|string)} lineNo The line number at which the error
160
+ * occurred.
161
+ * @param {?(number|string)} columnNo The column number at which the error
162
+ * occurred.
163
+ * @param {?Error} errorObj The actual Error object.
164
+ */
165
+ function traceKitWindowOnError(message, url, lineNo, columnNo, errorObj) {
166
+ var stack = null;
167
+
168
+ if (lastExceptionStack) {
169
+ TraceKit.computeStackTrace.augmentStackTraceWithInitialElement(lastExceptionStack, url, lineNo, message);
170
+ processLastException();
171
+ } else if (errorObj) {
172
+ stack = TraceKit.computeStackTrace(errorObj);
173
+ notifyHandlers(stack, true);
174
+ } else {
175
+ var location = {
176
+ 'url': url,
177
+ 'line': lineNo,
178
+ 'column': columnNo
179
+ };
180
+ location.func = TraceKit.computeStackTrace.guessFunctionName(location.url, location.line);
181
+ location.context = TraceKit.computeStackTrace.gatherContext(location.url, location.line);
182
+ stack = {
183
+ 'mode': 'onerror',
184
+ 'message': message,
185
+ 'stack': [location]
186
+ };
187
+
188
+ notifyHandlers(stack, true);
189
+ }
190
+
191
+ if (_oldOnerrorHandler) {
192
+ return _oldOnerrorHandler.apply(this, arguments);
193
+ }
194
+
195
+ return false;
196
+ }
197
+
198
+ function installGlobalHandler () {
199
+ if (_onErrorHandlerInstalled === true) {
200
+ return;
201
+ }
202
+ _oldOnerrorHandler = window.onerror;
203
+ window.onerror = traceKitWindowOnError;
204
+ _onErrorHandlerInstalled = true;
205
+ }
206
+
207
+ function processLastException() {
208
+ var _lastExceptionStack = lastExceptionStack,
209
+ _lastArgs = lastArgs;
210
+ lastArgs = null;
211
+ lastExceptionStack = null;
212
+ lastException = null;
213
+ notifyHandlers.apply(null, [_lastExceptionStack, false].concat(_lastArgs));
214
+ }
215
+ /**
216
+ * Reports an unhandled Error to TraceKit.
217
+ * @param {Error} ex
218
+ */
219
+ function report(ex) {
220
+ if (lastExceptionStack) {
221
+ if (lastException === ex) {
222
+ return; // already caught by an inner catch block, ignore
223
+ } else {
224
+ processLastException();
225
+ }
226
+ }
227
+
228
+ var stack = TraceKit.computeStackTrace(ex);
229
+ lastExceptionStack = stack;
230
+ lastException = ex;
231
+ lastArgs = _slice.call(arguments, 1);
232
+
233
+ // If the stack trace is incomplete, wait for 2 seconds for
234
+ // slow slow IE to see if onerror occurs or not before reporting
235
+ // this exception; otherwise, we will end up with an incomplete
236
+ // stack trace
237
+ window.setTimeout(function () {
238
+ if (lastException === ex) {
239
+ processLastException();
240
+ }
241
+ }, (stack.incomplete ? 2000 : 0));
242
+
243
+ throw ex; // re-throw to propagate to the top level (and cause window.onerror)
244
+ }
245
+
246
+ report.subscribe = subscribe;
247
+ report.unsubscribe = unsubscribe;
248
+ return report;
249
+ }());
250
+
251
+ /**
252
+ * TraceKit.computeStackTrace: cross-browser stack traces in JavaScript
253
+ *
254
+ * Syntax:
255
+ * s = TraceKit.computeStackTrace.ofCaller([depth])
256
+ * s = TraceKit.computeStackTrace(exception) // consider using TraceKit.report instead (see below)
257
+ * Returns:
258
+ * s.name - exception name
259
+ * s.message - exception message
260
+ * s.stack[i].url - JavaScript or HTML file URL
261
+ * s.stack[i].func - function name, or empty for anonymous functions (if guessing did not work)
262
+ * s.stack[i].args - arguments passed to the function, if known
263
+ * s.stack[i].line - line number, if known
264
+ * s.stack[i].column - column number, if known
265
+ * s.stack[i].context - an array of source code lines; the middle element corresponds to the correct line#
266
+ * s.mode - 'stack', 'stacktrace', 'multiline', 'callers', 'onerror', or 'failed' -- method used to collect the stack trace
267
+ *
268
+ * Supports:
269
+ * - Firefox: full stack trace with line numbers and unreliable column
270
+ * number on top frame
271
+ * - Opera 10: full stack trace with line and column numbers
272
+ * - Opera 9-: full stack trace with line numbers
273
+ * - Chrome: full stack trace with line and column numbers
274
+ * - Safari: line and column number for the topmost stacktrace element
275
+ * only
276
+ * - IE: no line numbers whatsoever
277
+ *
278
+ * Tries to guess names of anonymous functions by looking for assignments
279
+ * in the source code. In IE and Safari, we have to guess source file names
280
+ * by searching for function bodies inside all page scripts. This will not
281
+ * work for scripts that are loaded cross-domain.
282
+ * Here be dragons: some function names may be guessed incorrectly, and
283
+ * duplicate functions may be mismatched.
284
+ *
285
+ * TraceKit.computeStackTrace should only be used for tracing purposes.
286
+ * Logging of unhandled exceptions should be done with TraceKit.report,
287
+ * which builds on top of TraceKit.computeStackTrace and provides better
288
+ * IE support by utilizing the window.onerror event to retrieve information
289
+ * about the top of the stack.
290
+ *
291
+ * Note: In IE and Safari, no stack trace is recorded on the Error object,
292
+ * so computeStackTrace instead walks its *own* chain of callers.
293
+ * This means that:
294
+ * * in Safari, some methods may be missing from the stack trace;
295
+ * * in IE, the topmost function in the stack trace will always be the
296
+ * caller of computeStackTrace.
297
+ *
298
+ * This is okay for tracing (because you are likely to be calling
299
+ * computeStackTrace from the function you want to be the topmost element
300
+ * of the stack trace anyway), but not okay for logging unhandled
301
+ * exceptions (because your catch block will likely be far away from the
302
+ * inner function that actually caused the exception).
303
+ *
304
+ * Tracing example:
305
+ * function trace(message) {
306
+ * var stackInfo = TraceKit.computeStackTrace.ofCaller();
307
+ * var data = message + "\n";
308
+ * for(var i in stackInfo.stack) {
309
+ * var item = stackInfo.stack[i];
310
+ * data += (item.func || '[anonymous]') + "() in " + item.url + ":" + (item.line || '0') + "\n";
311
+ * }
312
+ * if (window.console)
313
+ * console.info(data);
314
+ * else
315
+ * alert(data);
316
+ * }
317
+ */
318
+ TraceKit.computeStackTrace = (function computeStackTraceWrapper() {
319
+ var debug = false,
320
+ sourceCache = {};
321
+
322
+ /**
323
+ * Attempts to retrieve source code via XMLHttpRequest, which is used
324
+ * to look up anonymous function names.
325
+ * @param {string} url URL of source code.
326
+ * @return {string} Source contents.
327
+ */
328
+ function loadSource(url) {
329
+ if (!TraceKit.remoteFetching) { //Only attempt request if remoteFetching is on.
330
+ return '';
331
+ }
332
+ try {
333
+ var getXHR = function() {
334
+ try {
335
+ return new window.XMLHttpRequest();
336
+ } catch (e) {
337
+ // explicitly bubble up the exception if not found
338
+ return new window.ActiveXObject('Microsoft.XMLHTTP');
339
+ }
340
+ };
341
+
342
+ var request = getXHR();
343
+ request.open('GET', url, false);
344
+ request.send('');
345
+ return request.responseText;
346
+ } catch (e) {
347
+ return '';
348
+ }
349
+ }
350
+
351
+ /**
352
+ * Retrieves source code from the source code cache.
353
+ * @param {string} url URL of source code.
354
+ * @return {Array.<string>} Source contents.
355
+ */
356
+ function getSource(url) {
357
+ if (typeof url !== 'string') {
358
+ return [];
359
+ }
360
+
361
+ if (!_has(sourceCache, url)) {
362
+ // URL needs to be able to fetched within the acceptable domain. Otherwise,
363
+ // cross-domain errors will be triggered.
364
+ var source = '';
365
+ var domain = '';
366
+ try { domain = document.domain; } catch (e) {}
367
+ if (url.indexOf(domain) !== -1) {
368
+ source = loadSource(url);
369
+ }
370
+ sourceCache[url] = source ? source.split('\n') : [];
371
+ }
372
+
373
+ return sourceCache[url];
374
+ }
375
+
376
+ /**
377
+ * Tries to use an externally loaded copy of source code to determine
378
+ * the name of a function by looking at the name of the variable it was
379
+ * assigned to, if any.
380
+ * @param {string} url URL of source code.
381
+ * @param {(string|number)} lineNo Line number in source code.
382
+ * @return {string} The function name, if discoverable.
383
+ */
384
+ function guessFunctionName(url, lineNo) {
385
+ var reFunctionArgNames = /function ([^(]*)\(([^)]*)\)/,
386
+ reGuessFunction = /['"]?([0-9A-Za-z$_]+)['"]?\s*[:=]\s*(function|eval|new Function)/,
387
+ line = '',
388
+ maxLines = 10,
389
+ source = getSource(url),
390
+ m;
391
+
392
+ if (!source.length) {
393
+ return UNKNOWN_FUNCTION;
394
+ }
395
+
396
+ // Walk backwards from the first line in the function until we find the line which
397
+ // matches the pattern above, which is the function definition
398
+ for (var i = 0; i < maxLines; ++i) {
399
+ line = source[lineNo - i] + line;
400
+
401
+ if (!_isUndefined(line)) {
402
+ if ((m = reGuessFunction.exec(line))) {
403
+ return m[1];
404
+ } else if ((m = reFunctionArgNames.exec(line))) {
405
+ return m[1];
406
+ }
407
+ }
408
+ }
409
+
410
+ return UNKNOWN_FUNCTION;
411
+ }
412
+
413
+ /**
414
+ * Retrieves the surrounding lines from where an exception occurred.
415
+ * @param {string} url URL of source code.
416
+ * @param {(string|number)} line Line number in source code to centre
417
+ * around for context.
418
+ * @return {?Array.<string>} Lines of source code.
419
+ */
420
+ function gatherContext(url, line) {
421
+ var source = getSource(url);
422
+
423
+ if (!source.length) {
424
+ return null;
425
+ }
426
+
427
+ var context = [],
428
+ // linesBefore & linesAfter are inclusive with the offending line.
429
+ // if linesOfContext is even, there will be one extra line
430
+ // *before* the offending line.
431
+ linesBefore = Math.floor(TraceKit.linesOfContext / 2),
432
+ // Add one extra line if linesOfContext is odd
433
+ linesAfter = linesBefore + (TraceKit.linesOfContext % 2),
434
+ start = Math.max(0, line - linesBefore - 1),
435
+ end = Math.min(source.length, line + linesAfter - 1);
436
+
437
+ line -= 1; // convert to 0-based index
438
+
439
+ for (var i = start; i < end; ++i) {
440
+ if (!_isUndefined(source[i])) {
441
+ context.push(source[i]);
442
+ }
443
+ }
444
+
445
+ return context.length > 0 ? context : null;
446
+ }
447
+
448
+ /**
449
+ * Escapes special characters, except for whitespace, in a string to be
450
+ * used inside a regular expression as a string literal.
451
+ * @param {string} text The string.
452
+ * @return {string} The escaped string literal.
453
+ */
454
+ function escapeRegExp(text) {
455
+ return text.replace(/[\-\[\]{}()*+?.,\\\^$|#]/g, '\\$&');
456
+ }
457
+
458
+ /**
459
+ * Escapes special characters in a string to be used inside a regular
460
+ * expression as a string literal. Also ensures that HTML entities will
461
+ * be matched the same as their literal friends.
462
+ * @param {string} body The string.
463
+ * @return {string} The escaped string.
464
+ */
465
+ function escapeCodeAsRegExpForMatchingInsideHTML(body) {
466
+ return escapeRegExp(body).replace('<', '(?:<|&lt;)').replace('>', '(?:>|&gt;)').replace('&', '(?:&|&amp;)').replace('"', '(?:"|&quot;)').replace(/\s+/g, '\\s+');
467
+ }
468
+
469
+ /**
470
+ * Determines where a code fragment occurs in the source code.
471
+ * @param {RegExp} re The function definition.
472
+ * @param {Array.<string>} urls A list of URLs to search.
473
+ * @return {?Object.<string, (string|number)>} An object containing
474
+ * the url, line, and column number of the defined function.
475
+ */
476
+ function findSourceInUrls(re, urls) {
477
+ var source, m;
478
+ for (var i = 0, j = urls.length; i < j; ++i) {
479
+ // console.log('searching', urls[i]);
480
+ if ((source = getSource(urls[i])).length) {
481
+ source = source.join('\n');
482
+ if ((m = re.exec(source))) {
483
+ // console.log('Found function in ' + urls[i]);
484
+
485
+ return {
486
+ 'url': urls[i],
487
+ 'line': source.substring(0, m.index).split('\n').length,
488
+ 'column': m.index - source.lastIndexOf('\n', m.index) - 1
489
+ };
490
+ }
491
+ }
492
+ }
493
+
494
+ // console.log('no match');
495
+
496
+ return null;
497
+ }
498
+
499
+ /**
500
+ * Determines at which column a code fragment occurs on a line of the
501
+ * source code.
502
+ * @param {string} fragment The code fragment.
503
+ * @param {string} url The URL to search.
504
+ * @param {(string|number)} line The line number to examine.
505
+ * @return {?number} The column number.
506
+ */
507
+ function findSourceInLine(fragment, url, line) {
508
+ var source = getSource(url),
509
+ re = new RegExp('\\b' + escapeRegExp(fragment) + '\\b'),
510
+ m;
511
+
512
+ line -= 1;
513
+
514
+ if (source && source.length > line && (m = re.exec(source[line]))) {
515
+ return m.index;
516
+ }
517
+
518
+ return null;
519
+ }
520
+
521
+ /**
522
+ * Determines where a function was defined within the source code.
523
+ * @param {(Function|string)} func A function reference or serialized
524
+ * function definition.
525
+ * @return {?Object.<string, (string|number)>} An object containing
526
+ * the url, line, and column number of the defined function.
527
+ */
528
+ function findSourceByFunctionBody(func) {
529
+ if (_isUndefined(document)) {
530
+ return;
531
+ }
532
+
533
+ var urls = [window.location.href],
534
+ scripts = document.getElementsByTagName('script'),
535
+ body,
536
+ code = '' + func,
537
+ codeRE = /^function(?:\s+([\w$]+))?\s*\(([\w\s,]*)\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/,
538
+ eventRE = /^function on([\w$]+)\s*\(event\)\s*\{\s*(\S[\s\S]*\S)\s*\}\s*$/,
539
+ re,
540
+ parts,
541
+ result;
542
+
543
+ for (var i = 0; i < scripts.length; ++i) {
544
+ var script = scripts[i];
545
+ if (script.src) {
546
+ urls.push(script.src);
547
+ }
548
+ }
549
+
550
+ if (!(parts = codeRE.exec(code))) {
551
+ re = new RegExp(escapeRegExp(code).replace(/\s+/g, '\\s+'));
552
+ }
553
+
554
+ // not sure if this is really necessary, but I don’t have a test
555
+ // corpus large enough to confirm that and it was in the original.
556
+ else {
557
+ var name = parts[1] ? '\\s+' + parts[1] : '',
558
+ args = parts[2].split(',').join('\\s*,\\s*');
559
+
560
+ body = escapeRegExp(parts[3]).replace(/;$/, ';?'); // semicolon is inserted if the function ends with a comment.replace(/\s+/g, '\\s+');
561
+ re = new RegExp('function' + name + '\\s*\\(\\s*' + args + '\\s*\\)\\s*{\\s*' + body + '\\s*}');
562
+ }
563
+
564
+ // look for a normal function definition
565
+ if ((result = findSourceInUrls(re, urls))) {
566
+ return result;
567
+ }
568
+
569
+ // look for an old-school event handler function
570
+ if ((parts = eventRE.exec(code))) {
571
+ var event = parts[1];
572
+ body = escapeCodeAsRegExpForMatchingInsideHTML(parts[2]);
573
+
574
+ // look for a function defined in HTML as an onXXX handler
575
+ re = new RegExp('on' + event + '=[\\\'"]\\s*' + body + '\\s*[\\\'"]', 'i');
576
+
577
+ if ((result = findSourceInUrls(re, urls[0]))) {
578
+ return result;
579
+ }
580
+
581
+ // look for ???
582
+ re = new RegExp(body);
583
+
584
+ if ((result = findSourceInUrls(re, urls))) {
585
+ return result;
586
+ }
587
+ }
588
+
589
+ return null;
590
+ }
591
+
592
+ // Contents of Exception in various browsers.
593
+ //
594
+ // SAFARI:
595
+ // ex.message = Can't find variable: qq
596
+ // ex.line = 59
597
+ // ex.sourceId = 580238192
598
+ // ex.sourceURL = http://...
599
+ // ex.expressionBeginOffset = 96
600
+ // ex.expressionCaretOffset = 98
601
+ // ex.expressionEndOffset = 98
602
+ // ex.name = ReferenceError
603
+ //
604
+ // FIREFOX:
605
+ // ex.message = qq is not defined
606
+ // ex.fileName = http://...
607
+ // ex.lineNumber = 59
608
+ // ex.columnNumber = 69
609
+ // ex.stack = ...stack trace... (see the example below)
610
+ // ex.name = ReferenceError
611
+ //
612
+ // CHROME:
613
+ // ex.message = qq is not defined
614
+ // ex.name = ReferenceError
615
+ // ex.type = not_defined
616
+ // ex.arguments = ['aa']
617
+ // ex.stack = ...stack trace...
618
+ //
619
+ // INTERNET EXPLORER:
620
+ // ex.message = ...
621
+ // ex.name = ReferenceError
622
+ //
623
+ // OPERA:
624
+ // ex.message = ...message... (see the example below)
625
+ // ex.name = ReferenceError
626
+ // ex.opera#sourceloc = 11 (pretty much useless, duplicates the info in ex.message)
627
+ // ex.stacktrace = n/a; see 'opera:config#UserPrefs|Exceptions Have Stacktrace'
628
+
629
+ /**
630
+ * Computes stack trace information from the stack property.
631
+ * Chrome and Gecko use this property.
632
+ * @param {Error} ex
633
+ * @return {?Object.<string, *>} Stack trace information.
634
+ */
635
+ function computeStackTraceFromStackProp(ex) {
636
+ if (!ex.stack) {
637
+ return null;
638
+ }
639
+
640
+ var chrome = /^\s*at (.*?) ?\(((?:file|https?|blob|chrome-extension|native|eval).*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i,
641
+ gecko = /^\s*(.*?)(?:\((.*?)\))?(?:^|@)((?:file|https?|blob|chrome|\[).*?)(?::(\d+))?(?::(\d+))?\s*$/i,
642
+ winjs = /^\s*at (?:((?:\[object object\])?.+) )?\(?((?:ms-appx|https?|blob):.*?):(\d+)(?::(\d+))?\)?\s*$/i,
643
+ lines = ex.stack.split('\n'),
644
+ stack = [],
645
+ parts,
646
+ element,
647
+ reference = /^(.*) is undefined$/.exec(ex.message);
648
+
649
+ for (var i = 0, j = lines.length; i < j; ++i) {
650
+ if ((parts = chrome.exec(lines[i]))) {
651
+ var isNative = parts[2] && parts[2].indexOf('native') !== -1;
652
+ element = {
653
+ 'url': !isNative ? parts[2] : null,
654
+ 'func': parts[1] || UNKNOWN_FUNCTION,
655
+ 'args': isNative ? [parts[2]] : [],
656
+ 'line': parts[3] ? +parts[3] : null,
657
+ 'column': parts[4] ? +parts[4] : null
658
+ };
659
+ } else if ( parts = winjs.exec(lines[i]) ) {
660
+ element = {
661
+ 'url': parts[2],
662
+ 'func': parts[1] || UNKNOWN_FUNCTION,
663
+ 'args': [],
664
+ 'line': +parts[3],
665
+ 'column': parts[4] ? +parts[4] : null
666
+ };
667
+ } else if ((parts = gecko.exec(lines[i]))) {
668
+ element = {
669
+ 'url': parts[3],
670
+ 'func': parts[1] || UNKNOWN_FUNCTION,
671
+ 'args': parts[2] ? parts[2].split(',') : [],
672
+ 'line': parts[4] ? +parts[4] : null,
673
+ 'column': parts[5] ? +parts[5] : null
674
+ };
675
+ } else {
676
+ continue;
677
+ }
678
+
679
+ if (!element.func && element.line) {
680
+ element.func = guessFunctionName(element.url, element.line);
681
+ }
682
+
683
+ if (element.line) {
684
+ element.context = gatherContext(element.url, element.line);
685
+ }
686
+
687
+ stack.push(element);
688
+ }
689
+
690
+ if (!stack.length) {
691
+ return null;
692
+ }
693
+
694
+ if (stack[0] && stack[0].line && !stack[0].column && reference) {
695
+ stack[0].column = findSourceInLine(reference[1], stack[0].url, stack[0].line);
696
+ } else if (!stack[0].column && !_isUndefined(ex.columnNumber)) {
697
+ // FireFox uses this awesome columnNumber property for its top frame
698
+ // Also note, Firefox's column number is 0-based and everything else expects 1-based,
699
+ // so adding 1
700
+ stack[0].column = ex.columnNumber + 1;
701
+ }
702
+
703
+ return {
704
+ 'mode': 'stack',
705
+ 'name': ex.name,
706
+ 'message': ex.message,
707
+ 'stack': stack
708
+ };
709
+ }
710
+
711
+ /**
712
+ * Computes stack trace information from the stacktrace property.
713
+ * Opera 10+ uses this property.
714
+ * @param {Error} ex
715
+ * @return {?Object.<string, *>} Stack trace information.
716
+ */
717
+ function computeStackTraceFromStacktraceProp(ex) {
718
+ // Access and store the stacktrace property before doing ANYTHING
719
+ // else to it because Opera is not very good at providing it
720
+ // reliably in other circumstances.
721
+ var stacktrace = ex.stacktrace;
722
+ if (!stacktrace) {
723
+ return;
724
+ }
725
+
726
+ var opera10Regex = / line (\d+).*script (?:in )?(\S+)(?:: in function (\S+))?$/i,
727
+ opera11Regex = / line (\d+), column (\d+)\s*(?:in (?:<anonymous function: ([^>]+)>|([^\)]+))\((.*)\))? in (.*):\s*$/i,
728
+ lines = stacktrace.split('\n'),
729
+ stack = [],
730
+ parts;
731
+
732
+ for (var line = 0; line < lines.length; line += 2) {
733
+ var element = null;
734
+ if ((parts = opera10Regex.exec(lines[line]))) {
735
+ element = {
736
+ 'url': parts[2],
737
+ 'line': +parts[1],
738
+ 'column': null,
739
+ 'func': parts[3],
740
+ 'args':[]
741
+ };
742
+ } else if ((parts = opera11Regex.exec(lines[line]))) {
743
+ element = {
744
+ 'url': parts[6],
745
+ 'line': +parts[1],
746
+ 'column': +parts[2],
747
+ 'func': parts[3] || parts[4],
748
+ 'args': parts[5] ? parts[5].split(',') : []
749
+ };
750
+ }
751
+
752
+ if (element) {
753
+ if (!element.func && element.line) {
754
+ element.func = guessFunctionName(element.url, element.line);
755
+ }
756
+ if (element.line) {
757
+ try {
758
+ element.context = gatherContext(element.url, element.line);
759
+ } catch (exc) {}
760
+ }
761
+
762
+ if (!element.context) {
763
+ element.context = [lines[line + 1]];
764
+ }
765
+
766
+ stack.push(element);
767
+ }
768
+ }
769
+
770
+ if (!stack.length) {
771
+ return null;
772
+ }
773
+
774
+ return {
775
+ 'mode': 'stacktrace',
776
+ 'name': ex.name,
777
+ 'message': ex.message,
778
+ 'stack': stack
779
+ };
780
+ }
781
+
782
+ /**
783
+ * NOT TESTED.
784
+ * Computes stack trace information from an error message that includes
785
+ * the stack trace.
786
+ * Opera 9 and earlier use this method if the option to show stack
787
+ * traces is turned on in opera:config.
788
+ * @param {Error} ex
789
+ * @return {?Object.<string, *>} Stack information.
790
+ */
791
+ function computeStackTraceFromOperaMultiLineMessage(ex) {
792
+ // TODO: Clean this function up
793
+ // Opera includes a stack trace into the exception message. An example is:
794
+ //
795
+ // Statement on line 3: Undefined variable: undefinedFunc
796
+ // Backtrace:
797
+ // Line 3 of linked script file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.js: In function zzz
798
+ // undefinedFunc(a);
799
+ // Line 7 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function yyy
800
+ // zzz(x, y, z);
801
+ // Line 3 of inline#1 script in file://localhost/Users/andreyvit/Projects/TraceKit/javascript-client/sample.html: In function xxx
802
+ // yyy(a, a, a);
803
+ // Line 1 of function script
804
+ // try { xxx('hi'); return false; } catch(ex) { TraceKit.report(ex); }
805
+ // ...
806
+
807
+ var lines = ex.message.split('\n');
808
+ if (lines.length < 4) {
809
+ return null;
810
+ }
811
+
812
+ var lineRE1 = /^\s*Line (\d+) of linked script ((?:file|https?|blob)\S+)(?:: in function (\S+))?\s*$/i,
813
+ lineRE2 = /^\s*Line (\d+) of inline#(\d+) script in ((?:file|https?|blob)\S+)(?:: in function (\S+))?\s*$/i,
814
+ lineRE3 = /^\s*Line (\d+) of function script\s*$/i,
815
+ stack = [],
816
+ scripts = document.getElementsByTagName('script'),
817
+ inlineScriptBlocks = [],
818
+ parts;
819
+
820
+ for (var s in scripts) {
821
+ if (_has(scripts, s) && !scripts[s].src) {
822
+ inlineScriptBlocks.push(scripts[s]);
823
+ }
824
+ }
825
+
826
+ for (var line = 2; line < lines.length; line += 2) {
827
+ var item = null;
828
+ if ((parts = lineRE1.exec(lines[line]))) {
829
+ item = {
830
+ 'url': parts[2],
831
+ 'func': parts[3],
832
+ 'args': [],
833
+ 'line': +parts[1],
834
+ 'column': null
835
+ };
836
+ } else if ((parts = lineRE2.exec(lines[line]))) {
837
+ item = {
838
+ 'url': parts[3],
839
+ 'func': parts[4],
840
+ 'args': [],
841
+ 'line': +parts[1],
842
+ 'column': null // TODO: Check to see if inline#1 (+parts[2]) points to the script number or column number.
843
+ };
844
+ var relativeLine = (+parts[1]); // relative to the start of the <SCRIPT> block
845
+ var script = inlineScriptBlocks[parts[2] - 1];
846
+ if (script) {
847
+ var source = getSource(item.url);
848
+ if (source) {
849
+ source = source.join('\n');
850
+ var pos = source.indexOf(script.innerText);
851
+ if (pos >= 0) {
852
+ item.line = relativeLine + source.substring(0, pos).split('\n').length;
853
+ }
854
+ }
855
+ }
856
+ } else if ((parts = lineRE3.exec(lines[line]))) {
857
+ var url = window.location.href.replace(/#.*$/, '');
858
+ var re = new RegExp(escapeCodeAsRegExpForMatchingInsideHTML(lines[line + 1]));
859
+ var src = findSourceInUrls(re, [url]);
860
+ item = {
861
+ 'url': url,
862
+ 'func': '',
863
+ 'args': [],
864
+ 'line': src ? src.line : parts[1],
865
+ 'column': null
866
+ };
867
+ }
868
+
869
+ if (item) {
870
+ if (!item.func) {
871
+ item.func = guessFunctionName(item.url, item.line);
872
+ }
873
+ var context = gatherContext(item.url, item.line);
874
+ var midline = (context ? context[Math.floor(context.length / 2)] : null);
875
+ if (context && midline.replace(/^\s*/, '') === lines[line + 1].replace(/^\s*/, '')) {
876
+ item.context = context;
877
+ } else {
878
+ // if (context) alert("Context mismatch. Correct midline:\n" + lines[i+1] + "\n\nMidline:\n" + midline + "\n\nContext:\n" + context.join("\n") + "\n\nURL:\n" + item.url);
879
+ item.context = [lines[line + 1]];
880
+ }
881
+ stack.push(item);
882
+ }
883
+ }
884
+ if (!stack.length) {
885
+ return null; // could not parse multiline exception message as Opera stack trace
886
+ }
887
+
888
+ return {
889
+ 'mode': 'multiline',
890
+ 'name': ex.name,
891
+ 'message': lines[0],
892
+ 'stack': stack
893
+ };
894
+ }
895
+
896
+ /**
897
+ * Adds information about the first frame to incomplete stack traces.
898
+ * Safari and IE require this to get complete data on the first frame.
899
+ * @param {Object.<string, *>} stackInfo Stack trace information from
900
+ * one of the compute* methods.
901
+ * @param {string} url The URL of the script that caused an error.
902
+ * @param {(number|string)} lineNo The line number of the script that
903
+ * caused an error.
904
+ * @param {string=} message The error generated by the browser, which
905
+ * hopefully contains the name of the object that caused the error.
906
+ * @return {boolean} Whether or not the stack information was
907
+ * augmented.
908
+ */
909
+ function augmentStackTraceWithInitialElement(stackInfo, url, lineNo, message) {
910
+ var initial = {
911
+ 'url': url,
912
+ 'line': lineNo
913
+ };
914
+
915
+ if (initial.url && initial.line) {
916
+ stackInfo.incomplete = false;
917
+
918
+ if (!initial.func) {
919
+ initial.func = guessFunctionName(initial.url, initial.line);
920
+ }
921
+
922
+ if (!initial.context) {
923
+ initial.context = gatherContext(initial.url, initial.line);
924
+ }
925
+
926
+ var reference = / '([^']+)' /.exec(message);
927
+ if (reference) {
928
+ initial.column = findSourceInLine(reference[1], initial.url, initial.line);
929
+ }
930
+
931
+ if (stackInfo.stack.length > 0) {
932
+ if (stackInfo.stack[0].url === initial.url) {
933
+ if (stackInfo.stack[0].line === initial.line) {
934
+ return false; // already in stack trace
935
+ } else if (!stackInfo.stack[0].line && stackInfo.stack[0].func === initial.func) {
936
+ stackInfo.stack[0].line = initial.line;
937
+ stackInfo.stack[0].context = initial.context;
938
+ return false;
939
+ }
940
+ }
941
+ }
942
+
943
+ stackInfo.stack.unshift(initial);
944
+ stackInfo.partial = true;
945
+ return true;
946
+ } else {
947
+ stackInfo.incomplete = true;
948
+ }
949
+
950
+ return false;
951
+ }
952
+
953
+ /**
954
+ * Computes stack trace information by walking the arguments.caller
955
+ * chain at the time the exception occurred. This will cause earlier
956
+ * frames to be missed but is the only way to get any stack trace in
957
+ * Safari and IE. The top frame is restored by
958
+ * {@link augmentStackTraceWithInitialElement}.
959
+ * @param {Error} ex
960
+ * @return {?Object.<string, *>} Stack trace information.
961
+ */
962
+ function computeStackTraceByWalkingCallerChain(ex, depth) {
963
+ var functionName = /function\s+([_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*)?\s*\(/i,
964
+ stack = [],
965
+ funcs = {},
966
+ recursion = false,
967
+ parts,
968
+ item,
969
+ source;
970
+
971
+ for (var curr = computeStackTraceByWalkingCallerChain.caller; curr && !recursion; curr = curr.caller) {
972
+ if (curr === computeStackTrace || curr === TraceKit.report) {
973
+ // console.log('skipping internal function');
974
+ continue;
975
+ }
976
+
977
+ item = {
978
+ 'url': null,
979
+ 'func': UNKNOWN_FUNCTION,
980
+ 'args': [],
981
+ 'line': null,
982
+ 'column': null
983
+ };
984
+
985
+ if (curr.name) {
986
+ item.func = curr.name;
987
+ } else if ((parts = functionName.exec(curr.toString()))) {
988
+ item.func = parts[1];
989
+ }
990
+
991
+ if (typeof item.func === 'undefined') {
992
+ try {
993
+ item.func = parts.input.substring(0, parts.input.indexOf('{'));
994
+ } catch (e) { }
995
+ }
996
+
997
+ if ((source = findSourceByFunctionBody(curr))) {
998
+ item.url = source.url;
999
+ item.line = source.line;
1000
+
1001
+ if (item.func === UNKNOWN_FUNCTION) {
1002
+ item.func = guessFunctionName(item.url, item.line);
1003
+ }
1004
+
1005
+ var reference = / '([^']+)' /.exec(ex.message || ex.description);
1006
+ if (reference) {
1007
+ item.column = findSourceInLine(reference[1], source.url, source.line);
1008
+ }
1009
+ }
1010
+
1011
+ if (funcs['' + curr]) {
1012
+ recursion = true;
1013
+ }else{
1014
+ funcs['' + curr] = true;
1015
+ }
1016
+
1017
+ stack.push(item);
1018
+ }
1019
+
1020
+ if (depth) {
1021
+ // console.log('depth is ' + depth);
1022
+ // console.log('stack is ' + stack.length);
1023
+ stack.splice(0, depth);
1024
+ }
1025
+
1026
+ var result = {
1027
+ 'mode': 'callers',
1028
+ 'name': ex.name,
1029
+ 'message': ex.message,
1030
+ 'stack': stack
1031
+ };
1032
+ augmentStackTraceWithInitialElement(result, ex.sourceURL || ex.fileName, ex.line || ex.lineNumber, ex.message || ex.description);
1033
+ return result;
1034
+ }
1035
+
1036
+ /**
1037
+ * Computes a stack trace for an exception.
1038
+ * @param {Error} ex
1039
+ * @param {(string|number)=} depth
1040
+ */
1041
+ function computeStackTrace(ex, depth) {
1042
+ var stack = null;
1043
+ depth = (depth == null ? 0 : +depth);
1044
+
1045
+ try {
1046
+ // This must be tried first because Opera 10 *destroys*
1047
+ // its stacktrace property if you try to access the stack
1048
+ // property first!!
1049
+ stack = computeStackTraceFromStacktraceProp(ex);
1050
+ if (stack) {
1051
+ return stack;
1052
+ }
1053
+ } catch (e) {
1054
+ if (debug) {
1055
+ throw e;
1056
+ }
1057
+ }
1058
+
1059
+ try {
1060
+ stack = computeStackTraceFromStackProp(ex);
1061
+ if (stack) {
1062
+ return stack;
1063
+ }
1064
+ } catch (e) {
1065
+ if (debug) {
1066
+ throw e;
1067
+ }
1068
+ }
1069
+
1070
+ try {
1071
+ stack = computeStackTraceFromOperaMultiLineMessage(ex);
1072
+ if (stack) {
1073
+ return stack;
1074
+ }
1075
+ } catch (e) {
1076
+ if (debug) {
1077
+ throw e;
1078
+ }
1079
+ }
1080
+
1081
+ try {
1082
+ stack = computeStackTraceByWalkingCallerChain(ex, depth + 1);
1083
+ if (stack) {
1084
+ return stack;
1085
+ }
1086
+ } catch (e) {
1087
+ if (debug) {
1088
+ throw e;
1089
+ }
1090
+ }
1091
+
1092
+ return {
1093
+ 'mode': 'failed'
1094
+ };
1095
+ }
1096
+
1097
+ /**
1098
+ * Logs a stacktrace starting from the previous call and working down.
1099
+ * @param {(number|string)=} depth How many frames deep to trace.
1100
+ * @return {Object.<string, *>} Stack trace information.
1101
+ */
1102
+ function computeStackTraceOfCaller(depth) {
1103
+ depth = (depth == null ? 0 : +depth) + 1; // "+ 1" because "ofCaller" should drop one frame
1104
+ try {
1105
+ throw new Error();
1106
+ } catch (ex) {
1107
+ return computeStackTrace(ex, depth + 1);
1108
+ }
1109
+ }
1110
+
1111
+ computeStackTrace.augmentStackTraceWithInitialElement = augmentStackTraceWithInitialElement;
1112
+ computeStackTrace.guessFunctionName = guessFunctionName;
1113
+ computeStackTrace.gatherContext = gatherContext;
1114
+ computeStackTrace.ofCaller = computeStackTraceOfCaller;
1115
+ computeStackTrace.getSource = getSource;
1116
+
1117
+ return computeStackTrace;
1118
+ }());
1119
+
1120
+ /**
1121
+ * Extends support for global error handling for asynchronous browser
1122
+ * functions. Adopted from Closure Library's errorhandler.js
1123
+ */
1124
+ TraceKit.extendToAsynchronousCallbacks = function () {
1125
+ var _helper = function _helper(fnName) {
1126
+ var originalFn = window[fnName];
1127
+ window[fnName] = function traceKitAsyncExtension() {
1128
+ // Make a copy of the arguments
1129
+ var args = _slice.call(arguments);
1130
+ var originalCallback = args[0];
1131
+ if (typeof (originalCallback) === 'function') {
1132
+ args[0] = TraceKit.wrap(originalCallback);
1133
+ }
1134
+ // IE < 9 doesn't support .call/.apply on setInterval/setTimeout, but it
1135
+ // also only supports 2 argument and doesn't care what "this" is, so we
1136
+ // can just call the original function directly.
1137
+ if (originalFn.apply) {
1138
+ return originalFn.apply(this, args);
1139
+ } else {
1140
+ return originalFn(args[0], args[1]);
1141
+ }
1142
+ };
1143
+ };
1144
+
1145
+ _helper('setTimeout');
1146
+ _helper('setInterval');
1147
+ };
1148
+
1149
+ //Default options:
1150
+ if (!TraceKit.remoteFetching) {
1151
+ TraceKit.remoteFetching = true;
1152
+ }
1153
+ if (!TraceKit.collectWindowErrors) {
1154
+ TraceKit.collectWindowErrors = true;
1155
+ }
1156
+ if (!TraceKit.linesOfContext || TraceKit.linesOfContext < 1) {
1157
+ // 5 lines before, the offending line, 5 lines after
1158
+ TraceKit.linesOfContext = 11;
1159
+ }
1160
+
1161
+
1162
+
1163
+ // Export to global object
1164
+ window.TraceKit = TraceKit;
1165
+
1166
+ }(typeof window !== 'undefined' ? window : global));