1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// Handles uncaught exceptions thrown by extensions. By default this is to
6// log an error message, but tests may override this behaviour.
7var handler = function(message, e) {
8  console.error(message);
9};
10
11/**
12 * Append the error description and stack trace to |message|.
13 *
14 * @param {string} message - The prefix of the error message.
15 * @param {Error|*} e - The thrown error object. This object is potentially
16 *   unsafe, because it could be generated by an extension.
17 * @param {string=} priorStackTrace - The stack trace to be appended to the
18 *   error message. This stack trace must not include stack frames of |e.stack|,
19 *   because both stack traces are concatenated. Overlapping stack traces will
20 *   confuse extension developers.
21 * @return {string} The formatted error message.
22 */
23function formatErrorMessage(message, e, priorStackTrace) {
24  if (e)
25    message += ': ' + safeErrorToString(e, false);
26
27  var stack;
28  try {
29    // If the stack was set, use it.
30    // |e.stack| could be void in the following common example:
31    // throw "Error message";
32    stack = $String.self(e && e.stack);
33  } catch (e) {}
34
35  // If a stack is not provided, capture a stack trace.
36  if (!priorStackTrace && !stack)
37    stack = getStackTrace();
38
39  stack = filterExtensionStackTrace(stack);
40  if (stack)
41    message += '\n' + stack;
42
43  // If an asynchronouse stack trace was set, append it.
44  if (priorStackTrace)
45    message += '\n' + priorStackTrace;
46
47  return message;
48}
49
50function filterExtensionStackTrace(stack) {
51  if (!stack)
52    return '';
53  // Remove stack frames in the stack trace that weren't associated with the
54  // extension, to not confuse extension developers with internal details.
55  stack = $String.split(stack, '\n');
56  stack = $Array.filter(stack, function(line) {
57    return $String.indexOf(line, 'chrome-extension://') >= 0;
58  });
59  return $Array.join(stack, '\n');
60}
61
62function getStackTrace() {
63  var e = {};
64  $Error.captureStackTrace(e, getStackTrace);
65  return e.stack;
66}
67
68function getExtensionStackTrace() {
69  return filterExtensionStackTrace(getStackTrace());
70}
71
72/**
73 * Convert an object to a string.
74 *
75 * @param {Error|*} e - A thrown object (possibly user-supplied).
76 * @param {boolean=} omitType - Whether to try to serialize |e.message| instead
77 *   of |e.toString()|.
78 * @return {string} The error message.
79 */
80function safeErrorToString(e, omitType) {
81  try {
82    return $String.self(omitType && e.message || e);
83  } catch (e) {
84    // This error is exceptional and could be triggered by
85    // throw {toString: function() { throw 'Haha' } };
86    return '(cannot get error message)';
87  }
88}
89
90/**
91 * Formats the error message and invokes the error handler.
92 *
93 * @param {string} message - Error message prefix.
94 * @param {Error|*} e - Thrown object.
95 * @param {string=} priorStackTrace - Error message suffix.
96 * @see formatErrorMessage
97 */
98exports.handle = function(message, e, priorStackTrace) {
99  message = formatErrorMessage(message, e, priorStackTrace);
100  handler(message, e);
101};
102
103// |newHandler| A function which matches |handler|.
104exports.setHandler = function(newHandler) {
105  handler = newHandler;
106};
107
108exports.getStackTrace = getStackTrace;
109exports.getExtensionStackTrace = getExtensionStackTrace;
110exports.safeErrorToString = safeErrorToString;
111