# lib/webdriver/stacktrace.js

    1// Copyright 2009 The Closure Library Authors. All Rights Reserved.2// Copyright 2012 Selenium comitters3// Copyright 2012 Software Freedom Conservancy4//5// Licensed under the Apache License, Version 2.0 (the "License");6// you may not use this file except in compliance with the License.7// You may obtain a copy of the License at8//9//      http://www.apache.org/licenses/LICENSE-2.010//11// Unless required by applicable law or agreed to in writing, software12// distributed under the License is distributed on an "AS-IS" BASIS,13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.14// See the License for the specific language governing permissions and15// limitations under the License.1617/**18 * @fileoverview Tools for parsing and pretty printing error stack traces. This19 * file is based on goog.testing.stacktrace.20 */2122goog.provide('webdriver.stacktrace');23goog.provide('webdriver.stacktrace.Snapshot');2425goog.require('goog.array');26goog.require('goog.string');27goog.require('goog.userAgent');28293031/**32 * Stores a snapshot of the stack trace at the time this instance was created.33 * The stack trace will always be adjusted to exclude this function call.34 * @param {number=} opt_slice The number of frames to remove from the top of35 *     the generated stack trace.36 * @constructor37 */38webdriver.stacktrace.Snapshot = function(opt_slice) {3940  /** @private {number} */41  this.slice_ = opt_slice || 0;4243  var error;44  if (webdriver.stacktrace.CAN_CAPTURE_STACK_TRACE_) {45    error = Error();46    Error.captureStackTrace(error, webdriver.stacktrace.Snapshot);47  } else {48    // Remove 1 extra frame for the call to this constructor.49    this.slice_ += 1;50    // IE will only create a stack trace when the Error is thrown.51    // We use null.x() to throw an exception instead of throw this.error_52    // because the closure compiler may optimize throws to a function call53    // in an attempt to minimize the binary size which in turn has the side54    // effect of adding an unwanted stack frame.55    try {56      null.x();57    } catch (e) {58      error = e;59    }60  }6162  /**63   * The error's stacktrace.  This must be accessed immediately to ensure Opera64   * computes the context correctly.65   * @private {string}66   */67  this.stack_ = webdriver.stacktrace.getStack_(error);68};697071/**72 * Whether the current environment supports the Error.captureStackTrace73 * function (as of 10/17/2012, only V8).74 * @private {boolean}75 * @const76 */77webdriver.stacktrace.CAN_CAPTURE_STACK_TRACE_ =78    goog.isFunction(Error.captureStackTrace);798081/**82 * Whether the current browser supports stack traces.83 *84 * @type {boolean}85 * @const86 */87webdriver.stacktrace.BROWSER_SUPPORTED =88    webdriver.stacktrace.CAN_CAPTURE_STACK_TRACE_ || (function() {89      try {90        throw Error();91      } catch (e) {92        return !!e.stack;93      }94    })();959697/**98 * The parsed stack trace. This list is lazily generated the first time it is99 * accessed.100 * @private {Array.<!webdriver.stacktrace.Frame>}101 */102webdriver.stacktrace.Snapshot.prototype.parsedStack_ = null;103104105/**106 * @return {!Array.<!webdriver.stacktrace.Frame>} The parsed stack trace.107 */108webdriver.stacktrace.Snapshot.prototype.getStacktrace = function() {109  if (goog.isNull(this.parsedStack_)) {110    this.parsedStack_ = webdriver.stacktrace.parse_(this.stack_);111    if (this.slice_) {112      this.parsedStack_ = goog.array.slice(this.parsedStack_, this.slice_);113    }114    delete this.slice_;115    delete this.stack_;116  }117  return this.parsedStack_;118};119120121122/**123 * Class representing one stack frame.124 * @param {(string|undefined)} context Context object, empty in case of global125 *     functions or if the browser doesn't provide this information.126 * @param {(string|undefined)} name Function name, empty in case of anonymous127 *     functions.128 * @param {(string|undefined)} alias Alias of the function if available. For129 *     example the function name will be 'c' and the alias will be 'b' if the130 *     function is defined as <code>a.b = function c() {};</code>.131 * @param {(string|undefined)} path File path or URL including line number and132 *     optionally column number separated by colons.133 * @constructor134 */135webdriver.stacktrace.Frame = function(context, name, alias, path) {136137  /** @private {string} */138  this.context_ = context || '';139140  /** @private {string} */141  this.name_ = name || '';142143  /** @private {string} */144  this.alias_ = alias || '';145146  /** @private {string} */147  this.path_ = path || '';148149  /** @private {string} */150  this.url_ = this.path_;151152  /** @private {number} */153  this.line_ = -1;154155  /** @private {number} */156  this.column_ = -1;157158  if (path) {159    var match = /:(\d+)(?::(\d+))?$/.exec(path);160    if (match) {161      this.line_ = Number(match[1]);162      this.column = Number(match[2] || -1);163      this.url_ = path.substr(0, match.index);164    }165  }166};167168169/**170 * Constant for an anonymous frame.171 * @private {!webdriver.stacktrace.Frame}172 * @const173 */174webdriver.stacktrace.ANONYMOUS_FRAME_ =175    new webdriver.stacktrace.Frame('', '', '', '');176177178/**179 * @return {string} The function name or empty string if the function is180 *     anonymous and the object field which it's assigned to is unknown.181 */182webdriver.stacktrace.Frame.prototype.getName = function() {183  return this.name_;184};185186187/**188 * @return {string} The url or empty string if it is unknown.189 */190webdriver.stacktrace.Frame.prototype.getUrl = function() {191  return this.url_;192};193194195/**196 * @return {number} The line number if known or -1 if it is unknown.197 */198webdriver.stacktrace.Frame.prototype.getLine = function() {199  return this.line_;200};201202203/**204 * @return {number} The column number if known and -1 if it is unknown.205 */206webdriver.stacktrace.Frame.prototype.getColumn = function() {207  return this.column_;208};209210211/**212 * @return {boolean} Whether the stack frame contains an anonymous function.213 */214webdriver.stacktrace.Frame.prototype.isAnonymous = function() {215  return !this.name_ || this.context_ == '[object Object]';216};217218219/**220 * Converts this frame to its string representation using V8's stack trace221 * format: http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi222 * @return {string} The string representation of this frame.223 * @override224 */225webdriver.stacktrace.Frame.prototype.toString = function() {226  var context = this.context_;227  if (context && context !== 'new ') {228    context += '.';229  }230  context += this.name_;231  context += this.alias_ ? ' [as ' + this.alias_ + ']' : '';232233  var path = this.path_ || '<anonymous>';234  return '    at ' + (context ? context + ' (' + path + ')' : path);235};236237238/**239 * Maximum length of a string that can be matched with a RegExp on240 * Firefox 3x. Exceeding this approximate length will cause string.match241 * to exceed Firefox's stack quota. This situation can be encountered242 * when goog.globalEval is invoked with a long argument; such as243 * when loading a module.244 * @private {number}245 * @const246 */247webdriver.stacktrace.MAX_FIREFOX_FRAMESTRING_LENGTH_ = 500000;248249250/**251 * RegExp pattern for JavaScript identifiers. We don't support Unicode252 * identifiers defined in ECMAScript v3.253 * @private {string}254 * @const255 */256webdriver.stacktrace.IDENTIFIER_PATTERN_ = '[a-zA-Z_$][\\w$]*';257258259/**260 * Pattern for a matching the type on a fully-qualified name. Forms an261 * optional sub-match on the type. For example, in "foo.bar.baz", will match on262 * "foo.bar".263 * @private {string}264 * @const265 */266webdriver.stacktrace.CONTEXT_PATTERN_ =267    '(' + webdriver.stacktrace.IDENTIFIER_PATTERN_ +268    '(?:\\.' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + ')*)\\.';269270271/**272 * Pattern for matching a fully qualified name. Will create two sub-matches:273 * the type (optional), and the name. For example, in "foo.bar.baz", will274 * match on ["foo.bar", "baz"].275 * @private {string}276 * @const277 */278webdriver.stacktrace.QUALIFIED_NAME_PATTERN_ =279    '(?:' + webdriver.stacktrace.CONTEXT_PATTERN_ + ')?' +280    '(' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + ')';281282283/**284 * RegExp pattern for function name alias in the V8 stack trace.285 * @private {string}286 * @const287 */288webdriver.stacktrace.V8_ALIAS_PATTERN_ =289    '(?: \\[as (' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + ')\\])?';290291292/**293 * RegExp pattern for function names and constructor calls in the V8 stack294 * trace.295 * @private {string}296 * @const297 */298webdriver.stacktrace.V8_FUNCTION_NAME_PATTERN_ =299    '(?:' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + '|<anonymous>)';300301302/**303 * RegExp pattern for the context of a function call in V8. Creates two304 * submatches, only one of which will ever match: either the namespace305 * identifier (with optional "new" keyword in the case of a constructor call),306 * or just the "new " phrase for a top level constructor call.307 * @private {string}308 * @const309 */310webdriver.stacktrace.V8_CONTEXT_PATTERN_ =311    '(?:((?:new )?(?:\\[object Object\\]|' +312    webdriver.stacktrace.IDENTIFIER_PATTERN_ +313    '(?:\\.' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + ')*)' +314    ')\\.|(new ))';315316317/**318 * RegExp pattern for function call in the V8 stack trace.319 * Creates 3 submatches with context object (optional), function name and320 * function alias (optional).321 * @private {string}322 * @const323 */324webdriver.stacktrace.V8_FUNCTION_CALL_PATTERN_ =325    ' (?:' + webdriver.stacktrace.V8_CONTEXT_PATTERN_ + ')?' +326    '(' + webdriver.stacktrace.V8_FUNCTION_NAME_PATTERN_ + ')' +327    webdriver.stacktrace.V8_ALIAS_PATTERN_;328329330/**331 * RegExp pattern for an URL + position inside the file.332 * @private {string}333 * @const334 */335webdriver.stacktrace.URL_PATTERN_ =336    '((?:http|https|file)://[^\\s]+|javascript:.*)';337338339/**340 * RegExp pattern for a location string in a V8 stack frame. Creates two341 * submatches for the location, one for enclosed in parentheticals and on342 * where the location appears alone (which will only occur if the location is343 * the only information in the frame).344 * @private {string}345 * @const346 * @see http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi347 */348webdriver.stacktrace.V8_LOCATION_PATTERN_ = ' (?:\\((.*)\\)|(.*))';349350351/**352 * Regular expression for parsing one stack frame in V8.353 * @private {!RegExp}354 * @const355 */356webdriver.stacktrace.V8_STACK_FRAME_REGEXP_ = new RegExp('^\\s+at' +357    // Prevent intersections with IE10 stack frame regex.358    '(?! (?:Anonymous function|Global code|eval code) )' +359    '(?:' + webdriver.stacktrace.V8_FUNCTION_CALL_PATTERN_ + ')?' +360    webdriver.stacktrace.V8_LOCATION_PATTERN_ + '$');361362363/**364 * RegExp pattern for function names in the Firefox stack trace.365 * Firefox has extended identifiers to deal with inner functions and anonymous366 * functions: https://bugzilla.mozilla.org/show_bug.cgi?id=433529#c9367 * @private {string}368 * @const369 */370webdriver.stacktrace.FIREFOX_FUNCTION_NAME_PATTERN_ =371    webdriver.stacktrace.IDENTIFIER_PATTERN_ + '[\\w./<$]*';372373374/**375 * RegExp pattern for function call in the Firefox stack trace.376 * Creates a submatch for the function name.377 * @private {string}378 * @const379 */380webdriver.stacktrace.FIREFOX_FUNCTION_CALL_PATTERN_ =381    '(' + webdriver.stacktrace.FIREFOX_FUNCTION_NAME_PATTERN_ + ')?' +382    '(?:\\(.*\\))?@';383384385/**386 * Regular expression for parsing one stack frame in Firefox.387 * @private {!RegExp}388 * @const389 */390webdriver.stacktrace.FIREFOX_STACK_FRAME_REGEXP_ = new RegExp('^' +391    webdriver.stacktrace.FIREFOX_FUNCTION_CALL_PATTERN_ +392    '(?::0|' + webdriver.stacktrace.URL_PATTERN_ + ')$');393394395/**396 * RegExp pattern for an anonymous function call in an Opera stack frame.397 * Creates 2 (optional) submatches: the context object and function name.398 * @private {string}399 * @const400 */401webdriver.stacktrace.OPERA_ANONYMOUS_FUNCTION_NAME_PATTERN_ =402    '<anonymous function(?:\\: ' +403    webdriver.stacktrace.QUALIFIED_NAME_PATTERN_ + ')?>';404405406/**407 * RegExp pattern for a function call in an Opera stack frame.408 * Creates 3 (optional) submatches: the function name (if not anonymous),409 * the aliased context object and the function name (if anonymous).410 * @private {string}411 * @const412 */413webdriver.stacktrace.OPERA_FUNCTION_CALL_PATTERN_ =414    '(?:(?:(' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + ')|' +415    webdriver.stacktrace.OPERA_ANONYMOUS_FUNCTION_NAME_PATTERN_ +416    ')(?:\\(.*\\)))?@';417418419/**420 * Regular expression for parsing on stack frame in Opera 11.68+421 * @private {!RegExp}422 * @const423 */424webdriver.stacktrace.OPERA_STACK_FRAME_REGEXP_ = new RegExp('^' +425    webdriver.stacktrace.OPERA_FUNCTION_CALL_PATTERN_ +426    webdriver.stacktrace.URL_PATTERN_ + '?$');427428429/**430 * RegExp pattern for function call in a Chakra (IE) stack trace. This431 * expression creates 2 submatches on the (optional) context and function name,432 * matching identifiers like 'foo.Bar.prototype.baz', 'Anonymous function',433 * 'eval code', and 'Global code'.434 * @private {string}435 * @const436 */437webdriver.stacktrace.CHAKRA_FUNCTION_CALL_PATTERN_ =438    '(?:(' + webdriver.stacktrace.IDENTIFIER_PATTERN_ +439    '(?:\\.' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + ')*)\\.)?' +440    '(' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + '(?:\\s+\\w+)*)';441442443/**444 * Regular expression for parsing on stack frame in Chakra (IE).445 * @private {!RegExp}446 * @const447 */448webdriver.stacktrace.CHAKRA_STACK_FRAME_REGEXP_ = new RegExp('^   at ' +449    webdriver.stacktrace.CHAKRA_FUNCTION_CALL_PATTERN_ +450    '\\s*(?:\\((.*)\\))$');451452453/**454 * Placeholder for an unparsable frame in a stack trace generated by455 * {@link goog.testing.stacktrace}.456 * @private {string}457 * @const458 */459webdriver.stacktrace.UNKNOWN_CLOSURE_FRAME_ = '> (unknown)';460461462/**463 * Representation of an anonymous frame in a stack trace generated by464 * {@link goog.testing.stacktrace}.465 * @private {string}466 * @const467 */468webdriver.stacktrace.ANONYMOUS_CLOSURE_FRAME_ = '> anonymous';469470471/**472 * Pattern for a function call in a Closure stack trace. Creates three optional473 * submatches: the context, function name, and alias.474 * @private {string}475 * @const476 */477webdriver.stacktrace.CLOSURE_FUNCTION_CALL_PATTERN_ =478    webdriver.stacktrace.QUALIFIED_NAME_PATTERN_ +479    '(?:\\(.*\\))?' +  // Ignore arguments if present.480    webdriver.stacktrace.V8_ALIAS_PATTERN_;481482483/**484 * Regular expression for parsing a stack frame generated by Closure's485 * {@link goog.testing.stacktrace}.486 * @private {!RegExp}487 * @const488 */489webdriver.stacktrace.CLOSURE_STACK_FRAME_REGEXP_ = new RegExp('^> ' +490    '(?:' + webdriver.stacktrace.CLOSURE_FUNCTION_CALL_PATTERN_ +491    '(?: at )?)?' +492    '(?:(.*:\\d+:\\d+)|' + webdriver.stacktrace.URL_PATTERN_ + ')?$');493494495/**496 * Parses one stack frame.497 * @param {string} frameStr The stack frame as string.498 * @return {webdriver.stacktrace.Frame} Stack frame object or null if the499 *     parsing failed.500 * @private501 */502webdriver.stacktrace.parseStackFrame_ = function(frameStr) {503  var m = frameStr.match(webdriver.stacktrace.V8_STACK_FRAME_REGEXP_);504  if (m) {505    return new webdriver.stacktrace.Frame(506        m[1] || m[2], m[3], m[4], m[5] || m[6]);507  }508509  if (frameStr.length >510      webdriver.stacktrace.MAX_FIREFOX_FRAMESTRING_LENGTH_) {511    return webdriver.stacktrace.parseLongFirefoxFrame_(frameStr);512  }513514  m = frameStr.match(webdriver.stacktrace.FIREFOX_STACK_FRAME_REGEXP_);515  if (m) {516    return new webdriver.stacktrace.Frame('', m[1], '', m[2]);517  }518519  m = frameStr.match(webdriver.stacktrace.OPERA_STACK_FRAME_REGEXP_);520  if (m) {521    return new webdriver.stacktrace.Frame(m[2], m[1] || m[3], '', m[4]);522  }523524  m = frameStr.match(webdriver.stacktrace.CHAKRA_STACK_FRAME_REGEXP_);525  if (m) {526    return new webdriver.stacktrace.Frame(m[1], m[2], '', m[3]);527  }528529  if (frameStr == webdriver.stacktrace.UNKNOWN_CLOSURE_FRAME_ ||530      frameStr == webdriver.stacktrace.ANONYMOUS_CLOSURE_FRAME_) {531    return webdriver.stacktrace.ANONYMOUS_FRAME_;532  }533534  m = frameStr.match(webdriver.stacktrace.CLOSURE_STACK_FRAME_REGEXP_);535  if (m) {536    return new webdriver.stacktrace.Frame(m[1], m[2], m[3], m[4] || m[5]);537  }538539  return null;540};541542543/**544 * Parses a long firefox stack frame.545 * @param {string} frameStr The stack frame as string.546 * @return {!webdriver.stacktrace.Frame} Stack frame object.547 * @private548 */549webdriver.stacktrace.parseLongFirefoxFrame_ = function(frameStr) {550  var firstParen = frameStr.indexOf('(');551  var lastAmpersand = frameStr.lastIndexOf('@');552  var lastColon = frameStr.lastIndexOf(':');553  var functionName = '';554  if ((firstParen >= 0) && (firstParen < lastAmpersand)) {555    functionName = frameStr.substring(0, firstParen);556  }557  var loc = '';558  if ((lastAmpersand >= 0) && (lastAmpersand + 1 < lastColon)) {559    loc = frameStr.substring(lastAmpersand + 1);560  }561  return new webdriver.stacktrace.Frame('', functionName, '', loc);562};563564565/**566 * Get an error's stack trace with the error string trimmed.567 * V8 prepends the string representation of an error to its stack trace.568 * This function trims the string so that the stack trace can be parsed569 * consistently with the other JS engines.570 * @param {(Error|goog.testing.JsUnitException)} error The error.571 * @return {string} The stack trace string.572 * @private573 */574webdriver.stacktrace.getStack_ = function(error) {575  if (!error) {576    return '';577  }578  var stack = error.stack || error.stackTrace || '';579  var errorStr = error + '\n';580  if (goog.string.startsWith(stack, errorStr)) {581    stack = stack.substring(errorStr.length);582  }583  return stack;584};585586587/**588 * Formats an error's stack trace.589 * @param {!(Error|goog.testing.JsUnitException)} error The error to format.590 * @return {!(Error|goog.testing.JsUnitException)} The formatted error.591 */592webdriver.stacktrace.format = function(error) {593  var stack = webdriver.stacktrace.getStack_(error);594  var frames = webdriver.stacktrace.parse_(stack);595596  // If the original stack is in an unexpected format, our formatted stack597  // trace will be a bunch of "    at <anonymous>" lines. If this is the case,598  // just return the error unmodified to avoid losing information. This is599  // necessary since the user may have defined a custom stack formatter in600  // V8 via Error.prepareStackTrace. See issue 7994.601  var isAnonymousFrame = function(frame) {602    return frame.toString() === '    at <anonymous>';603  };604  if (frames.length && goog.array.every(frames, isAnonymousFrame)) {605    return error;606  }607608  // Older versions of IE simply return [object Error] for toString(), so609  // only use that as a last resort.610  var errorStr = '';611  if (error.message) {612    errorStr = (error.name ? error.name + ': ' : '') + error.message;613  } else {614    errorStr = error.toString();615  }616617  // Ensure the error is in the V8 style with the error's string representation618  // prepended to the stack.619  error.stack = errorStr + '\n' + frames.join('\n');620  return error;621};622623624/**625 * Parses an Error object's stack trace.626 * @param {string} stack The stack trace.627 * @return {!Array.<!webdriver.stacktrace.Frame>} Stack frames. The628 *     unrecognized frames will be nulled out.629 * @private630 */631webdriver.stacktrace.parse_ = function(stack) {632  if (!stack) {633    return [];634  }635636  var lines = stack.637      replace(/\s*$/, '').638      split('\n');639  var frames = [];640  for (var i = 0; i < lines.length; i++) {641    var frame = webdriver.stacktrace.parseStackFrame_(lines[i]);642    // The first two frames will be:643    //   webdriver.stacktrace.Snapshot()644    //   webdriver.stacktrace.get()645    // In the case of Opera, sometimes an extra frame is injected in the next646    // frame with a reported line number of zero. The next line detects that647    // case and skips that frame.648    if (!(goog.userAgent.OPERA && i == 2 && frame.getLine() == 0)) {649      frames.push(frame || webdriver.stacktrace.ANONYMOUS_FRAME_);650    }651  }652  return frames;653};654655656/**657 * Gets the native stack trace if available otherwise follows the call chain.658 * The generated trace will exclude all frames up to and including the call to659 * this function.660 * @return {!Array.<!webdriver.stacktrace.Frame>} The frames of the stack trace.661 */662webdriver.stacktrace.get = function() {663  return new webdriver.stacktrace.Snapshot(1).getStacktrace();664};