1010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)// Copyright 2014 The Chromium Authors. All rights reserved.
2010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
3010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)// found in the LICENSE file.
4010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)
5010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)// Handles uncaught exceptions thrown by extensions. By default this is to
6010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)// log an error message, but tests may override this behaviour.
7010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)var handler = function(message, e) {
8010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  console.error(message);
9010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)};
10010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)
1103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)/**
1203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) * Append the error description and stack trace to |message|.
1303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) *
1403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) * @param {string} message - The prefix of the error message.
1503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) * @param {Error|*} e - The thrown error object. This object is potentially
1603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) *   unsafe, because it could be generated by an extension.
1703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) * @param {string=} priorStackTrace - The stack trace to be appended to the
1803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) *   error message. This stack trace must not include stack frames of |e.stack|,
1903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) *   because both stack traces are concatenated. Overlapping stack traces will
2003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) *   confuse extension developers.
2103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) * @return {string} The formatted error message.
2203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) */
2303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)function formatErrorMessage(message, e, priorStackTrace) {
2403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  if (e)
2503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    message += ': ' + safeErrorToString(e, false);
2603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
2703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  var stack;
2803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  try {
2903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    // If the stack was set, use it.
3003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    // |e.stack| could be void in the following common example:
3103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    // throw "Error message";
3203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    stack = $String.self(e && e.stack);
3303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  } catch (e) {}
3403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
3503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  // If a stack is not provided, capture a stack trace.
3603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  if (!priorStackTrace && !stack)
3703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    stack = getStackTrace();
3803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
3903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  stack = filterExtensionStackTrace(stack);
4003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  if (stack)
4103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    message += '\n' + stack;
4203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
4303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  // If an asynchronouse stack trace was set, append it.
4403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  if (priorStackTrace)
4503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    message += '\n' + priorStackTrace;
4603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
4703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  return message;
4803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)}
4903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
5003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)function filterExtensionStackTrace(stack) {
5103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  if (!stack)
5203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    return '';
5303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  // Remove stack frames in the stack trace that weren't associated with the
5403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  // extension, to not confuse extension developers with internal details.
5503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  stack = $String.split(stack, '\n');
5603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  stack = $Array.filter(stack, function(line) {
5703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    return $String.indexOf(line, 'chrome-extension://') >= 0;
5803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  });
5903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  return $Array.join(stack, '\n');
6003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)}
6103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
6203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)function getStackTrace() {
6303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  var e = {};
6403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  $Error.captureStackTrace(e, getStackTrace);
6503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  return e.stack;
6603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)}
6703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
6803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)function getExtensionStackTrace() {
6903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  return filterExtensionStackTrace(getStackTrace());
7003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)}
7103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
7203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)/**
7303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) * Convert an object to a string.
7403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) *
7503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) * @param {Error|*} e - A thrown object (possibly user-supplied).
7603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) * @param {boolean=} omitType - Whether to try to serialize |e.message| instead
7703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) *   of |e.toString()|.
7803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) * @return {string} The error message.
7903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) */
8003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)function safeErrorToString(e, omitType) {
8103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  try {
8203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    return $String.self(omitType && e.message || e);
8303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  } catch (e) {
8403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    // This error is exceptional and could be triggered by
8503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    // throw {toString: function() { throw 'Haha' } };
8603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)    return '(cannot get error message)';
8703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  }
8803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)}
8903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
9003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)/**
9103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) * Formats the error message and invokes the error handler.
9203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) *
9303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) * @param {string} message - Error message prefix.
9403b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) * @param {Error|*} e - Thrown object.
9503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) * @param {string=} priorStackTrace - Error message suffix.
9603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) * @see formatErrorMessage
9703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) */
9803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)exports.handle = function(message, e, priorStackTrace) {
9903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)  message = formatErrorMessage(message, e, priorStackTrace);
100010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  handler(message, e);
101010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)};
102010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)
10303b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)// |newHandler| A function which matches |handler|.
104010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)exports.setHandler = function(newHandler) {
105010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)  handler = newHandler;
106010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)};
10703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)
10803b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)exports.getStackTrace = getStackTrace;
10903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)exports.getExtensionStackTrace = getExtensionStackTrace;
11003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)exports.safeErrorToString = safeErrorToString;
111