whoopsie 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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));