# 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('^    at' +357    '(?:' + webdriver.stacktrace.V8_FUNCTION_CALL_PATTERN_ + ')?' +358    webdriver.stacktrace.V8_LOCATION_PATTERN_ + '$');359360361/**362 * RegExp pattern for function names in the Firefox stack trace.363 * Firefox has extended identifiers to deal with inner functions and anonymous364 * functions: https://bugzilla.mozilla.org/show_bug.cgi?id=433529#c9365 * @private {string}366 * @const367 */368webdriver.stacktrace.FIREFOX_FUNCTION_NAME_PATTERN_ =369    webdriver.stacktrace.IDENTIFIER_PATTERN_ + '[\\w./<$]*';370371372/**373 * RegExp pattern for function call in the Firefox stack trace.374 * Creates a submatch for the function name.375 * @private {string}376 * @const377 */378webdriver.stacktrace.FIREFOX_FUNCTION_CALL_PATTERN_ =379    '(' + webdriver.stacktrace.FIREFOX_FUNCTION_NAME_PATTERN_ + ')?' +380    '(?:\\(.*\\))?@';381382383/**384 * Regular expression for parsing one stack frame in Firefox.385 * @private {!RegExp}386 * @const387 */388webdriver.stacktrace.FIREFOX_STACK_FRAME_REGEXP_ = new RegExp('^' +389    webdriver.stacktrace.FIREFOX_FUNCTION_CALL_PATTERN_ +390    '(?::0|' + webdriver.stacktrace.URL_PATTERN_ + ')$');391392393/**394 * RegExp pattern for an anonymous function call in an Opera stack frame.395 * Creates 2 (optional) submatches: the context object and function name.396 * @private {string}397 * @const398 */399webdriver.stacktrace.OPERA_ANONYMOUS_FUNCTION_NAME_PATTERN_ =400    '<anonymous function(?:\\: ' +401    webdriver.stacktrace.QUALIFIED_NAME_PATTERN_ + ')?>';402403404/**405 * RegExp pattern for a function call in an Opera stack frame.406 * Creates 3 (optional) submatches: the function name (if not anonymous),407 * the aliased context object and the function name (if anonymous).408 * @private {string}409 * @const410 */411webdriver.stacktrace.OPERA_FUNCTION_CALL_PATTERN_ =412    '(?:(?:(' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + ')|' +413    webdriver.stacktrace.OPERA_ANONYMOUS_FUNCTION_NAME_PATTERN_ +414    ')(?:\\(.*\\)))?@';415416417/**418 * Regular expression for parsing on stack frame in Opera 11.68+419 * @private {!RegExp}420 * @const421 */422webdriver.stacktrace.OPERA_STACK_FRAME_REGEXP_ = new RegExp('^' +423    webdriver.stacktrace.OPERA_FUNCTION_CALL_PATTERN_ +424    webdriver.stacktrace.URL_PATTERN_ + '?$');425426427/**428 * RegExp pattern for function call in a Chakra (IE) stack trace. This429 * expression creates 2 submatches on the (optional) context and function name,430 * matching identifiers like 'foo.Bar.prototype.baz', 'Anonymous function',431 * 'eval code', and 'Global code'.432 * @private {string}433 * @const434 */435webdriver.stacktrace.CHAKRA_FUNCTION_CALL_PATTERN_ =436    '(?:(' + webdriver.stacktrace.IDENTIFIER_PATTERN_ +437    '(?:\\.' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + ')*)\\.)?' +438    '(' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + '(?:\\s+\\w+)*)';439440441/**442 * Regular expression for parsing on stack frame in Chakra (IE).443 * @private {!RegExp}444 * @const445 */446webdriver.stacktrace.CHAKRA_STACK_FRAME_REGEXP_ = new RegExp('^   at ' +447    webdriver.stacktrace.CHAKRA_FUNCTION_CALL_PATTERN_ +448    '\\s*(?:\\((.*)\\))$');449450451/**452 * Placeholder for an unparsable frame in a stack trace generated by453 * {@link goog.testing.stacktrace}.454 * @private {string}455 * @const456 */457webdriver.stacktrace.UNKNOWN_CLOSURE_FRAME_ = '> (unknown)';458459460/**461 * Representation of an anonymous frame in a stack trace generated by462 * {@link goog.testing.stacktrace}.463 * @private {string}464 * @const465 */466webdriver.stacktrace.ANONYMOUS_CLOSURE_FRAME_ = '> anonymous';467468469/**470 * Pattern for a function call in a Closure stack trace. Creates three optional471 * submatches: the context, function name, and alias.472 * @private {string}473 * @const474 */475webdriver.stacktrace.CLOSURE_FUNCTION_CALL_PATTERN_ =476    webdriver.stacktrace.QUALIFIED_NAME_PATTERN_ +477    '(?:\\(.*\\))?' +  // Ignore arguments if present.478    webdriver.stacktrace.V8_ALIAS_PATTERN_;479480481/**482 * Regular expression for parsing a stack frame generated by Closure's483 * {@link goog.testing.stacktrace}.484 * @private {!RegExp}485 * @const486 */487webdriver.stacktrace.CLOSURE_STACK_FRAME_REGEXP_ = new RegExp('^> ' +488    '(?:' + webdriver.stacktrace.CLOSURE_FUNCTION_CALL_PATTERN_ +489    '(?: at )?)?' +490    '(?:(.*:\\d+:\\d+)|' + webdriver.stacktrace.URL_PATTERN_ + ')?$');491492493/**494 * Parses one stack frame.495 * @param {string} frameStr The stack frame as string.496 * @return {webdriver.stacktrace.Frame} Stack frame object or null if the497 *     parsing failed.498 * @private499 */500webdriver.stacktrace.parseStackFrame_ = function(frameStr) {501  var m = frameStr.match(webdriver.stacktrace.V8_STACK_FRAME_REGEXP_);502  if (m) {503    return new webdriver.stacktrace.Frame(504        m[1] || m[2], m[3], m[4], m[5] || m[6]);505  }506507  if (frameStr.length >508      webdriver.stacktrace.MAX_FIREFOX_FRAMESTRING_LENGTH_) {509    return webdriver.stacktrace.parseLongFirefoxFrame_(frameStr);510  }511512  m = frameStr.match(webdriver.stacktrace.FIREFOX_STACK_FRAME_REGEXP_);513  if (m) {514    return new webdriver.stacktrace.Frame('', m[1], '', m[2]);515  }516517  m = frameStr.match(webdriver.stacktrace.OPERA_STACK_FRAME_REGEXP_);518  if (m) {519    return new webdriver.stacktrace.Frame(m[2], m[1] || m[3], '', m[4]);520  }521522  m = frameStr.match(webdriver.stacktrace.CHAKRA_STACK_FRAME_REGEXP_);523  if (m) {524    return new webdriver.stacktrace.Frame(m[1], m[2], '', m[3]);525  }526527  if (frameStr == webdriver.stacktrace.UNKNOWN_CLOSURE_FRAME_ ||528      frameStr == webdriver.stacktrace.ANONYMOUS_CLOSURE_FRAME_) {529    return webdriver.stacktrace.ANONYMOUS_FRAME_;530  }531532  m = frameStr.match(webdriver.stacktrace.CLOSURE_STACK_FRAME_REGEXP_);533  if (m) {534    return new webdriver.stacktrace.Frame(m[1], m[2], m[3], m[4] || m[5]);535  }536537  return null;538};539540541/**542 * Parses a long firefox stack frame.543 * @param {string} frameStr The stack frame as string.544 * @return {!webdriver.stacktrace.Frame} Stack frame object.545 * @private546 */547webdriver.stacktrace.parseLongFirefoxFrame_ = function(frameStr) {548  var firstParen = frameStr.indexOf('(');549  var lastAmpersand = frameStr.lastIndexOf('@');550  var lastColon = frameStr.lastIndexOf(':');551  var functionName = '';552  if ((firstParen >= 0) && (firstParen < lastAmpersand)) {553    functionName = frameStr.substring(0, firstParen);554  }555  var loc = '';556  if ((lastAmpersand >= 0) && (lastAmpersand + 1 < lastColon)) {557    loc = frameStr.substring(lastAmpersand + 1);558  }559  return new webdriver.stacktrace.Frame('', functionName, '', loc);560};561562563/**564 * Get an error's stack trace with the error string trimmed.565 * V8 prepends the string representation of an error to its stack trace.566 * This function trims the string so that the stack trace can be parsed567 * consistently with the other JS engines.568 * @param {(Error|goog.testing.JsUnitException)} error The error.569 * @return {string} The stack trace string.570 * @private571 */572webdriver.stacktrace.getStack_ = function(error) {573  if (!error) {574    return '';575  }576  var stack = error.stack || error.stackTrace || '';577  var errorStr = error + '\n';578  if (goog.string.startsWith(stack, errorStr)) {579    stack = stack.substring(errorStr.length);580  }581  return stack;582};583584585/**586 * Formats an error's stack trace.587 * @param {!(Error|goog.testing.JsUnitException)} error The error to format.588 * @return {!(Error|goog.testing.JsUnitException)} The formatted error.589 */590webdriver.stacktrace.format = function(error) {591  var stack = webdriver.stacktrace.getStack_(error);592  var frames = webdriver.stacktrace.parse_(stack);593594  // Older versions of IE simply return [object Error] for toString(), so595  // only use that as a last resort.596  var errorStr = '';597  if (error.message) {598    errorStr = (error.name ? error.name + ': ' : '') + error.message;599  } else {600    errorStr = error.toString();601  }602603  // Ensure the error is in the V8 style with the error's string representation604  // prepended to the stack.605  error.stack = errorStr + '\n' + frames.join('\n');606  return error;607};608609610/**611 * Parses an Error object's stack trace.612 * @param {string} stack The stack trace.613 * @return {!Array.<!webdriver.stacktrace.Frame>} Stack frames. The614 *     unrecognized frames will be nulled out.615 * @private616 */617webdriver.stacktrace.parse_ = function(stack) {618  if (!stack) {619    return [];620  }621622  var lines = stack.623      replace(/\s*$/, '').624      split('\n');625  var frames = [];626  for (var i = 0; i < lines.length; i++) {627    var frame = webdriver.stacktrace.parseStackFrame_(lines[i]);628    // The first two frames will be:629    //   webdriver.stacktrace.Snapshot()630    //   webdriver.stacktrace.get()631    // In the case of Opera, sometimes an extra frame is injected in the next632    // frame with a reported line number of zero. The next line detects that633    // case and skips that frame.634    if (!(goog.userAgent.OPERA && i == 2 && frame.getLine() == 0)) {635      frames.push(frame || webdriver.stacktrace.ANONYMOUS_FRAME_);636    }637  }638  return frames;639};640641642/**643 * Gets the native stack trace if available otherwise follows the call chain.644 * The generated trace will exclude all frames up to and including the call to645 * this function.646 * @return {!Array.<!webdriver.stacktrace.Frame>} The frames of the stack trace.647 */648webdriver.stacktrace.get = function() {649  return new webdriver.stacktrace.Snapshot(1).getStacktrace();650};