// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Handles uncaught exceptions thrown by extensions. By default this is to // log an error message, but tests may override this behaviour. var handler = function(message, e) { console.error(message); }; /** * Append the error description and stack trace to |message|. * * @param {string} message - The prefix of the error message. * @param {Error|*} e - The thrown error object. This object is potentially * unsafe, because it could be generated by an extension. * @param {string=} priorStackTrace - The stack trace to be appended to the * error message. This stack trace must not include stack frames of |e.stack|, * because both stack traces are concatenated. Overlapping stack traces will * confuse extension developers. * @return {string} The formatted error message. */ function formatErrorMessage(message, e, priorStackTrace) { if (e) message += ': ' + safeErrorToString(e, false); var stack; try { // If the stack was set, use it. // |e.stack| could be void in the following common example: // throw "Error message"; stack = $String.self(e && e.stack); } catch (e) {} // If a stack is not provided, capture a stack trace. if (!priorStackTrace && !stack) stack = getStackTrace(); stack = filterExtensionStackTrace(stack); if (stack) message += '\n' + stack; // If an asynchronouse stack trace was set, append it. if (priorStackTrace) message += '\n' + priorStackTrace; return message; } function filterExtensionStackTrace(stack) { if (!stack) return ''; // Remove stack frames in the stack trace that weren't associated with the // extension, to not confuse extension developers with internal details. stack = $String.split(stack, '\n'); stack = $Array.filter(stack, function(line) { return $String.indexOf(line, 'chrome-extension://') >= 0; }); return $Array.join(stack, '\n'); } function getStackTrace() { var e = {}; $Error.captureStackTrace(e, getStackTrace); return e.stack; } function getExtensionStackTrace() { return filterExtensionStackTrace(getStackTrace()); } /** * Convert an object to a string. * * @param {Error|*} e - A thrown object (possibly user-supplied). * @param {boolean=} omitType - Whether to try to serialize |e.message| instead * of |e.toString()|. * @return {string} The error message. */ function safeErrorToString(e, omitType) { try { return $String.self(omitType && e.message || e); } catch (e) { // This error is exceptional and could be triggered by // throw {toString: function() { throw 'Haha' } }; return '(cannot get error message)'; } } /** * Formats the error message and invokes the error handler. * * @param {string} message - Error message prefix. * @param {Error|*} e - Thrown object. * @param {string=} priorStackTrace - Error message suffix. * @see formatErrorMessage */ exports.handle = function(message, e, priorStackTrace) { message = formatErrorMessage(message, e, priorStackTrace); handler(message, e); }; // |newHandler| A function which matches |handler|. exports.setHandler = function(newHandler) { handler = newHandler; }; exports.getStackTrace = getStackTrace; exports.getExtensionStackTrace = getExtensionStackTrace; exports.safeErrorToString = safeErrorToString;