| // Licensed to the Software Freedom Conservancy (SFC) under one | |
| // or more contributor license agreements. See the NOTICE file | |
| // distributed with this work for additional information | |
| // regarding copyright ownership. The SFC licenses this file | |
| // to you under the Apache License, Version 2.0 (the | |
| // "License"); you may not use this file except in compliance | |
| // with the License. You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, | |
| // software distributed under the License is distributed on an | |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| // KIND, either express or implied. See the License for the | |
| // specific language governing permissions and limitations | |
| // under the License. | |
| /** | |
| * @fileoverview Tools for parsing and pretty printing error stack traces. This | |
| * file is based on goog.testing.stacktrace. | |
| */ | |
| goog.provide('webdriver.stacktrace'); | |
| goog.provide('webdriver.stacktrace.Snapshot'); | |
| goog.require('goog.array'); | |
| goog.require('goog.string'); | |
| goog.require('goog.userAgent'); | |
| /** | |
| * Stores a snapshot of the stack trace at the time this instance was created. | |
| * The stack trace will always be adjusted to exclude this function call. | |
| * @param {number=} opt_slice The number of frames to remove from the top of | |
| * the generated stack trace. | |
| * @constructor | |
| */ | |
| webdriver.stacktrace.Snapshot = function(opt_slice) { | |
| /** @private {number} */ | |
| this.slice_ = opt_slice || 0; | |
| var error; | |
| if (webdriver.stacktrace.CAN_CAPTURE_STACK_TRACE_) { | |
| error = Error(); | |
| Error.captureStackTrace(error, webdriver.stacktrace.Snapshot); | |
| } else { | |
| // Remove 1 extra frame for the call to this constructor. | |
| this.slice_ += 1; | |
| // IE will only create a stack trace when the Error is thrown. | |
| // We use null.x() to throw an exception instead of throw this.error_ | |
| // because the closure compiler may optimize throws to a function call | |
| // in an attempt to minimize the binary size which in turn has the side | |
| // effect of adding an unwanted stack frame. | |
| try { | |
| null.x(); | |
| } catch (e) { | |
| error = e; | |
| } | |
| } | |
| /** | |
| * The error's stacktrace. | |
| * @private {string} | |
| */ | |
| this.stack_ = webdriver.stacktrace.getStack(error); | |
| }; | |
| /** | |
| * Whether the current environment supports the Error.captureStackTrace | |
| * function (as of 10/17/2012, only V8). | |
| * @private {boolean} | |
| * @const | |
| */ | |
| webdriver.stacktrace.CAN_CAPTURE_STACK_TRACE_ = | |
| goog.isFunction(Error.captureStackTrace); | |
| /** | |
| * Whether the current browser supports stack traces. | |
| * | |
| * @type {boolean} | |
| * @const | |
| */ | |
| webdriver.stacktrace.BROWSER_SUPPORTED = | |
| webdriver.stacktrace.CAN_CAPTURE_STACK_TRACE_ || (function() { | |
| try { | |
| throw Error(); | |
| } catch (e) { | |
| return !!e.stack; | |
| } | |
| })(); | |
| /** | |
| * The parsed stack trace. This list is lazily generated the first time it is | |
| * accessed. | |
| * @private {Array.<!webdriver.stacktrace.Frame>} | |
| */ | |
| webdriver.stacktrace.Snapshot.prototype.parsedStack_ = null; | |
| /** | |
| * @return {!Array.<!webdriver.stacktrace.Frame>} The parsed stack trace. | |
| */ | |
| webdriver.stacktrace.Snapshot.prototype.getStacktrace = function() { | |
| if (goog.isNull(this.parsedStack_)) { | |
| this.parsedStack_ = webdriver.stacktrace.parse_(this.stack_); | |
| if (this.slice_) { | |
| this.parsedStack_ = goog.array.slice(this.parsedStack_, this.slice_); | |
| } | |
| delete this.slice_; | |
| delete this.stack_; | |
| } | |
| return this.parsedStack_; | |
| }; | |
| /** | |
| * Class representing one stack frame. | |
| * @param {(string|undefined)} context Context object, empty in case of global | |
| * functions or if the browser doesn't provide this information. | |
| * @param {(string|undefined)} name Function name, empty in case of anonymous | |
| * functions. | |
| * @param {(string|undefined)} alias Alias of the function if available. For | |
| * example the function name will be 'c' and the alias will be 'b' if the | |
| * function is defined as <code>a.b = function c() {};</code>. | |
| * @param {(string|undefined)} path File path or URL including line number and | |
| * optionally column number separated by colons. | |
| * @constructor | |
| */ | |
| webdriver.stacktrace.Frame = function(context, name, alias, path) { | |
| /** @private {string} */ | |
| this.context_ = context || ''; | |
| /** @private {string} */ | |
| this.name_ = name || ''; | |
| /** @private {string} */ | |
| this.alias_ = alias || ''; | |
| /** @private {string} */ | |
| this.path_ = path || ''; | |
| /** @private {string} */ | |
| this.url_ = this.path_; | |
| /** @private {number} */ | |
| this.line_ = -1; | |
| /** @private {number} */ | |
| this.column_ = -1; | |
| if (path) { | |
| var match = /:(\d+)(?::(\d+))?$/.exec(path); | |
| if (match) { | |
| this.line_ = Number(match[1]); | |
| this.column = Number(match[2] || -1); | |
| this.url_ = path.substr(0, match.index); | |
| } | |
| } | |
| }; | |
| /** | |
| * Constant for an anonymous frame. | |
| * @private {!webdriver.stacktrace.Frame} | |
| * @const | |
| */ | |
| webdriver.stacktrace.ANONYMOUS_FRAME_ = | |
| new webdriver.stacktrace.Frame('', '', '', ''); | |
| /** | |
| * @return {string} The function name or empty string if the function is | |
| * anonymous and the object field which it's assigned to is unknown. | |
| */ | |
| webdriver.stacktrace.Frame.prototype.getName = function() { | |
| return this.name_; | |
| }; | |
| /** | |
| * @return {string} The url or empty string if it is unknown. | |
| */ | |
| webdriver.stacktrace.Frame.prototype.getUrl = function() { | |
| return this.url_; | |
| }; | |
| /** | |
| * @return {number} The line number if known or -1 if it is unknown. | |
| */ | |
| webdriver.stacktrace.Frame.prototype.getLine = function() { | |
| return this.line_; | |
| }; | |
| /** | |
| * @return {number} The column number if known and -1 if it is unknown. | |
| */ | |
| webdriver.stacktrace.Frame.prototype.getColumn = function() { | |
| return this.column_; | |
| }; | |
| /** | |
| * @return {boolean} Whether the stack frame contains an anonymous function. | |
| */ | |
| webdriver.stacktrace.Frame.prototype.isAnonymous = function() { | |
| return !this.name_ || this.context_ == '[object Object]'; | |
| }; | |
| /** | |
| * Converts this frame to its string representation using V8's stack trace | |
| * format: http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi | |
| * @return {string} The string representation of this frame. | |
| * @override | |
| */ | |
| webdriver.stacktrace.Frame.prototype.toString = function() { | |
| var context = this.context_; | |
| if (context && context !== 'new ') { | |
| context += '.'; | |
| } | |
| context += this.name_; | |
| context += this.alias_ ? ' [as ' + this.alias_ + ']' : ''; | |
| var path = this.path_ || '<anonymous>'; | |
| return ' at ' + (context ? context + ' (' + path + ')' : path); | |
| }; | |
| /** | |
| * Maximum length of a string that can be matched with a RegExp on | |
| * Firefox 3x. Exceeding this approximate length will cause string.match | |
| * to exceed Firefox's stack quota. This situation can be encountered | |
| * when goog.globalEval is invoked with a long argument; such as | |
| * when loading a module. | |
| * @private {number} | |
| * @const | |
| */ | |
| webdriver.stacktrace.MAX_FIREFOX_FRAMESTRING_LENGTH_ = 500000; | |
| /** | |
| * RegExp pattern for JavaScript identifiers. We don't support Unicode | |
| * identifiers defined in ECMAScript v3. | |
| * @private {string} | |
| * @const | |
| */ | |
| webdriver.stacktrace.IDENTIFIER_PATTERN_ = '[a-zA-Z_$][\\w$]*'; | |
| /** | |
| * Pattern for a matching the type on a fully-qualified name. Forms an | |
| * optional sub-match on the type. For example, in "foo.bar.baz", will match on | |
| * "foo.bar". | |
| * @private {string} | |
| * @const | |
| */ | |
| webdriver.stacktrace.CONTEXT_PATTERN_ = | |
| '(' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + | |
| '(?:\\.' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + ')*)\\.'; | |
| /** | |
| * Pattern for matching a fully qualified name. Will create two sub-matches: | |
| * the type (optional), and the name. For example, in "foo.bar.baz", will | |
| * match on ["foo.bar", "baz"]. | |
| * @private {string} | |
| * @const | |
| */ | |
| webdriver.stacktrace.QUALIFIED_NAME_PATTERN_ = | |
| '(?:' + webdriver.stacktrace.CONTEXT_PATTERN_ + ')?' + | |
| '(' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + ')'; | |
| /** | |
| * RegExp pattern for function name alias in the V8 stack trace. | |
| * @private {string} | |
| * @const | |
| */ | |
| webdriver.stacktrace.V8_ALIAS_PATTERN_ = | |
| '(?: \\[as (' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + ')\\])?'; | |
| /** | |
| * RegExp pattern for function names and constructor calls in the V8 stack | |
| * trace. | |
| * @private {string} | |
| * @const | |
| */ | |
| webdriver.stacktrace.V8_FUNCTION_NAME_PATTERN_ = | |
| '(?:' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + '|<anonymous>)'; | |
| /** | |
| * RegExp pattern for the context of a function call in V8. Creates two | |
| * submatches, only one of which will ever match: either the namespace | |
| * identifier (with optional "new" keyword in the case of a constructor call), | |
| * or just the "new " phrase for a top level constructor call. | |
| * @private {string} | |
| * @const | |
| */ | |
| webdriver.stacktrace.V8_CONTEXT_PATTERN_ = | |
| '(?:((?:new )?(?:\\[object Object\\]|' + | |
| webdriver.stacktrace.IDENTIFIER_PATTERN_ + | |
| '(?:\\.' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + ')*)' + | |
| ')\\.|(new ))'; | |
| /** | |
| * RegExp pattern for function call in the V8 stack trace. | |
| * Creates 3 submatches with context object (optional), function name and | |
| * function alias (optional). | |
| * @private {string} | |
| * @const | |
| */ | |
| webdriver.stacktrace.V8_FUNCTION_CALL_PATTERN_ = | |
| ' (?:' + webdriver.stacktrace.V8_CONTEXT_PATTERN_ + ')?' + | |
| '(' + webdriver.stacktrace.V8_FUNCTION_NAME_PATTERN_ + ')' + | |
| webdriver.stacktrace.V8_ALIAS_PATTERN_; | |
| /** | |
| * RegExp pattern for an URL + position inside the file. | |
| * @private {string} | |
| * @const | |
| */ | |
| webdriver.stacktrace.URL_PATTERN_ = | |
| '((?:http|https|file)://[^\\s]+|javascript:.*)'; | |
| /** | |
| * RegExp pattern for a location string in a V8 stack frame. Creates two | |
| * submatches for the location, one for enclosed in parentheticals and on | |
| * where the location appears alone (which will only occur if the location is | |
| * the only information in the frame). | |
| * @private {string} | |
| * @const | |
| * @see http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi | |
| */ | |
| webdriver.stacktrace.V8_LOCATION_PATTERN_ = ' (?:\\((.*)\\)|(.*))'; | |
| /** | |
| * Regular expression for parsing one stack frame in V8. | |
| * @private {!RegExp} | |
| * @const | |
| */ | |
| webdriver.stacktrace.V8_STACK_FRAME_REGEXP_ = new RegExp('^\\s+at' + | |
| // Prevent intersections with IE10 stack frame regex. | |
| '(?! (?:Anonymous function|Global code|eval code) )' + | |
| '(?:' + webdriver.stacktrace.V8_FUNCTION_CALL_PATTERN_ + ')?' + | |
| webdriver.stacktrace.V8_LOCATION_PATTERN_ + '$'); | |
| /** | |
| * RegExp pattern for function names in the Firefox stack trace. | |
| * Firefox has extended identifiers to deal with inner functions and anonymous | |
| * functions: https://bugzilla.mozilla.org/show_bug.cgi?id=433529#c9 | |
| * @private {string} | |
| * @const | |
| */ | |
| webdriver.stacktrace.FIREFOX_FUNCTION_NAME_PATTERN_ = | |
| webdriver.stacktrace.IDENTIFIER_PATTERN_ + '[\\w./<$]*'; | |
| /** | |
| * RegExp pattern for function call in the Firefox stack trace. | |
| * Creates a submatch for the function name. | |
| * @private {string} | |
| * @const | |
| */ | |
| webdriver.stacktrace.FIREFOX_FUNCTION_CALL_PATTERN_ = | |
| '(' + webdriver.stacktrace.FIREFOX_FUNCTION_NAME_PATTERN_ + ')?' + | |
| '(?:\\(.*\\))?@'; | |
| /** | |
| * Regular expression for parsing one stack frame in Firefox. | |
| * @private {!RegExp} | |
| * @const | |
| */ | |
| webdriver.stacktrace.FIREFOX_STACK_FRAME_REGEXP_ = new RegExp('^' + | |
| webdriver.stacktrace.FIREFOX_FUNCTION_CALL_PATTERN_ + | |
| '(?::0|' + webdriver.stacktrace.URL_PATTERN_ + ')$'); | |
| /** | |
| * RegExp pattern for function call in a Chakra (IE) stack trace. This | |
| * expression creates 2 submatches on the (optional) context and function name, | |
| * matching identifiers like 'foo.Bar.prototype.baz', 'Anonymous function', | |
| * 'eval code', and 'Global code'. | |
| * @private {string} | |
| * @const | |
| */ | |
| webdriver.stacktrace.CHAKRA_FUNCTION_CALL_PATTERN_ = | |
| '(?:(' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + | |
| '(?:\\.' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + ')*)\\.)?' + | |
| '(' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + '(?:\\s+\\w+)*)'; | |
| /** | |
| * Regular expression for parsing on stack frame in Chakra (IE). | |
| * @private {!RegExp} | |
| * @const | |
| */ | |
| webdriver.stacktrace.CHAKRA_STACK_FRAME_REGEXP_ = new RegExp('^ at ' + | |
| webdriver.stacktrace.CHAKRA_FUNCTION_CALL_PATTERN_ + | |
| '\\s*(?:\\((.*)\\))$'); | |
| /** | |
| * Placeholder for an unparsable frame in a stack trace generated by | |
| * {@link goog.testing.stacktrace}. | |
| * @private {string} | |
| * @const | |
| */ | |
| webdriver.stacktrace.UNKNOWN_CLOSURE_FRAME_ = '> (unknown)'; | |
| /** | |
| * Representation of an anonymous frame in a stack trace generated by | |
| * {@link goog.testing.stacktrace}. | |
| * @private {string} | |
| * @const | |
| */ | |
| webdriver.stacktrace.ANONYMOUS_CLOSURE_FRAME_ = '> anonymous'; | |
| /** | |
| * Pattern for a function call in a Closure stack trace. Creates three optional | |
| * submatches: the context, function name, and alias. | |
| * @private {string} | |
| * @const | |
| */ | |
| webdriver.stacktrace.CLOSURE_FUNCTION_CALL_PATTERN_ = | |
| webdriver.stacktrace.QUALIFIED_NAME_PATTERN_ + | |
| '(?:\\(.*\\))?' + // Ignore arguments if present. | |
| webdriver.stacktrace.V8_ALIAS_PATTERN_; | |
| /** | |
| * Regular expression for parsing a stack frame generated by Closure's | |
| * {@link goog.testing.stacktrace}. | |
| * @private {!RegExp} | |
| * @const | |
| */ | |
| webdriver.stacktrace.CLOSURE_STACK_FRAME_REGEXP_ = new RegExp('^> ' + | |
| '(?:' + webdriver.stacktrace.CLOSURE_FUNCTION_CALL_PATTERN_ + | |
| '(?: at )?)?' + | |
| '(?:(.*:\\d+:\\d+)|' + webdriver.stacktrace.URL_PATTERN_ + ')?$'); | |
| /** | |
| * Parses one stack frame. | |
| * @param {string} frameStr The stack frame as string. | |
| * @return {webdriver.stacktrace.Frame} Stack frame object or null if the | |
| * parsing failed. | |
| * @private | |
| */ | |
| webdriver.stacktrace.parseStackFrame_ = function(frameStr) { | |
| var m = frameStr.match(webdriver.stacktrace.V8_STACK_FRAME_REGEXP_); | |
| if (m) { | |
| return new webdriver.stacktrace.Frame( | |
| m[1] || m[2], m[3], m[4], m[5] || m[6]); | |
| } | |
| if (frameStr.length > | |
| webdriver.stacktrace.MAX_FIREFOX_FRAMESTRING_LENGTH_) { | |
| return webdriver.stacktrace.parseLongFirefoxFrame_(frameStr); | |
| } | |
| m = frameStr.match(webdriver.stacktrace.FIREFOX_STACK_FRAME_REGEXP_); | |
| if (m) { | |
| return new webdriver.stacktrace.Frame('', m[1], '', m[2]); | |
| } | |
| m = frameStr.match(webdriver.stacktrace.CHAKRA_STACK_FRAME_REGEXP_); | |
| if (m) { | |
| return new webdriver.stacktrace.Frame(m[1], m[2], '', m[3]); | |
| } | |
| if (frameStr == webdriver.stacktrace.UNKNOWN_CLOSURE_FRAME_ || | |
| frameStr == webdriver.stacktrace.ANONYMOUS_CLOSURE_FRAME_) { | |
| return webdriver.stacktrace.ANONYMOUS_FRAME_; | |
| } | |
| m = frameStr.match(webdriver.stacktrace.CLOSURE_STACK_FRAME_REGEXP_); | |
| if (m) { | |
| return new webdriver.stacktrace.Frame(m[1], m[2], m[3], m[4] || m[5]); | |
| } | |
| return null; | |
| }; | |
| /** | |
| * Parses a long firefox stack frame. | |
| * @param {string} frameStr The stack frame as string. | |
| * @return {!webdriver.stacktrace.Frame} Stack frame object. | |
| * @private | |
| */ | |
| webdriver.stacktrace.parseLongFirefoxFrame_ = function(frameStr) { | |
| var firstParen = frameStr.indexOf('('); | |
| var lastAmpersand = frameStr.lastIndexOf('@'); | |
| var lastColon = frameStr.lastIndexOf(':'); | |
| var functionName = ''; | |
| if ((firstParen >= 0) && (firstParen < lastAmpersand)) { | |
| functionName = frameStr.substring(0, firstParen); | |
| } | |
| var loc = ''; | |
| if ((lastAmpersand >= 0) && (lastAmpersand + 1 < lastColon)) { | |
| loc = frameStr.substring(lastAmpersand + 1); | |
| } | |
| return new webdriver.stacktrace.Frame('', functionName, '', loc); | |
| }; | |
| /** | |
| * Get an error's stack trace with the error string trimmed. | |
| * V8 prepends the string representation of an error to its stack trace. | |
| * This function trims the string so that the stack trace can be parsed | |
| * consistently with the other JS engines. | |
| * @param {(Error|goog.testing.JsUnitException)} error The error. | |
| * @return {string} The stack trace string. | |
| */ | |
| webdriver.stacktrace.getStack = function(error) { | |
| if (!error) { | |
| return ''; | |
| } | |
| var stack = error.stack || error.stackTrace || ''; | |
| var errorStr = error + '\n'; | |
| if (goog.string.startsWith(stack, errorStr)) { | |
| stack = stack.substring(errorStr.length); | |
| } | |
| return stack; | |
| }; | |
| /** | |
| * Formats an error's stack trace. | |
| * @param {!(Error|goog.testing.JsUnitException)} error The error to format. | |
| * @return {!(Error|goog.testing.JsUnitException)} The formatted error. | |
| */ | |
| webdriver.stacktrace.format = function(error) { | |
| var stack = webdriver.stacktrace.getStack(error); | |
| var frames = webdriver.stacktrace.parse_(stack); | |
| // If the original stack is in an unexpected format, our formatted stack | |
| // trace will be a bunch of " at <anonymous>" lines. If this is the case, | |
| // just return the error unmodified to avoid losing information. This is | |
| // necessary since the user may have defined a custom stack formatter in | |
| // V8 via Error.prepareStackTrace. See issue 7994. | |
| var isAnonymousFrame = function(frame) { | |
| return frame.toString() === ' at <anonymous>'; | |
| }; | |
| if (frames.length && goog.array.every(frames, isAnonymousFrame)) { | |
| return error; | |
| } | |
| // Older versions of IE simply return [object Error] for toString(), so | |
| // only use that as a last resort. | |
| var errorStr = ''; | |
| if (error.message) { | |
| errorStr = (error.name ? error.name + ': ' : '') + error.message; | |
| } else { | |
| errorStr = error.toString(); | |
| } | |
| // Ensure the error is in the V8 style with the error's string representation | |
| // prepended to the stack. | |
| error.stack = errorStr + '\n' + frames.join('\n'); | |
| return error; | |
| }; | |
| /** | |
| * Parses an Error object's stack trace. | |
| * @param {string} stack The stack trace. | |
| * @return {!Array.<!webdriver.stacktrace.Frame>} Stack frames. The | |
| * unrecognized frames will be nulled out. | |
| * @private | |
| */ | |
| webdriver.stacktrace.parse_ = function(stack) { | |
| if (!stack) { | |
| return []; | |
| } | |
| var lines = stack. | |
| replace(/\s*$/, ''). | |
| split('\n'); | |
| var frames = []; | |
| for (var i = 0; i < lines.length; i++) { | |
| var frame = webdriver.stacktrace.parseStackFrame_(lines[i]); | |
| // The first two frames will be: | |
| // webdriver.stacktrace.Snapshot() | |
| // webdriver.stacktrace.get() | |
| frames.push(frame || webdriver.stacktrace.ANONYMOUS_FRAME_); | |
| } | |
| return frames; | |
| }; | |
| /** | |
| * Gets the native stack trace if available otherwise follows the call chain. | |
| * The generated trace will exclude all frames up to and including the call to | |
| * this function. | |
| * @return {!Array.<!webdriver.stacktrace.Frame>} The frames of the stack trace. | |
| */ | |
| webdriver.stacktrace.get = function() { | |
| return new webdriver.stacktrace.Snapshot(1).getStacktrace(); | |
| }; |