Path: blob/trunk/javascript/webdriver/stacktrace.js
2867 views
// Licensed to the Software Freedom Conservancy (SFC) under one1// or more contributor license agreements. See the NOTICE file2// distributed with this work for additional information3// regarding copyright ownership. The SFC licenses this file4// to you under the Apache License, Version 2.0 (the5// "License"); you may not use this file except in compliance6// with the License. You may obtain a copy of the License at7//8// http://www.apache.org/licenses/LICENSE-2.09//10// Unless required by applicable law or agreed to in writing,11// software distributed under the License is distributed on an12// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY13// KIND, either express or implied. See the License for the14// specific language governing permissions and limitations15// 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} */41this.slice_ = opt_slice || 0;4243var error;44if (webdriver.stacktrace.CAN_CAPTURE_STACK_TRACE_) {45error = Error();46Error.captureStackTrace(error, webdriver.stacktrace.Snapshot);47} else {48// Remove 1 extra frame for the call to this constructor.49this.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.55try {56null.x();57} catch (e) {58error = e;59}60}6162/**63* The error's stacktrace.64* @private {string}65*/66this.stack_ = webdriver.stacktrace.getStack(error);67};686970/**71* Whether the current environment supports the Error.captureStackTrace72* function (as of 10/17/2012, only V8).73* @private {boolean}74* @const75*/76webdriver.stacktrace.CAN_CAPTURE_STACK_TRACE_ =77goog.isFunction(Error.captureStackTrace);787980/**81* Whether the current browser supports stack traces.82*83* @type {boolean}84* @const85*/86webdriver.stacktrace.BROWSER_SUPPORTED =87webdriver.stacktrace.CAN_CAPTURE_STACK_TRACE_ || (function() {88try {89throw Error();90} catch (e) {91return !!e.stack;92}93})();949596/**97* The parsed stack trace. This list is lazily generated the first time it is98* accessed.99* @private {Array.<!webdriver.stacktrace.Frame>}100*/101webdriver.stacktrace.Snapshot.prototype.parsedStack_ = null;102103104/**105* @return {!Array.<!webdriver.stacktrace.Frame>} The parsed stack trace.106*/107webdriver.stacktrace.Snapshot.prototype.getStacktrace = function() {108if (goog.isNull(this.parsedStack_)) {109this.parsedStack_ = webdriver.stacktrace.parse_(this.stack_);110if (this.slice_) {111this.parsedStack_ = goog.array.slice(this.parsedStack_, this.slice_);112}113delete this.slice_;114delete this.stack_;115}116return this.parsedStack_;117};118119120121/**122* Class representing one stack frame.123* @param {(string|undefined)} context Context object, empty in case of global124* functions or if the browser doesn't provide this information.125* @param {(string|undefined)} name Function name, empty in case of anonymous126* functions.127* @param {(string|undefined)} alias Alias of the function if available. For128* example the function name will be 'c' and the alias will be 'b' if the129* function is defined as <code>a.b = function c() {};</code>.130* @param {(string|undefined)} path File path or URL including line number and131* optionally column number separated by colons.132* @constructor133*/134webdriver.stacktrace.Frame = function(context, name, alias, path) {135136/** @private {string} */137this.context_ = context || '';138139/** @private {string} */140this.name_ = name || '';141142/** @private {string} */143this.alias_ = alias || '';144145/** @private {string} */146this.path_ = path || '';147148/** @private {string} */149this.url_ = this.path_;150151/** @private {number} */152this.line_ = -1;153154/** @private {number} */155this.column_ = -1;156157if (path) {158var match = /:(\d+)(?::(\d+))?$/.exec(path);159if (match) {160this.line_ = Number(match[1]);161this.column = Number(match[2] || -1);162this.url_ = path.substr(0, match.index);163}164}165};166167168/**169* Constant for an anonymous frame.170* @private {!webdriver.stacktrace.Frame}171* @const172*/173webdriver.stacktrace.ANONYMOUS_FRAME_ =174new webdriver.stacktrace.Frame('', '', '', '');175176177/**178* @return {string} The function name or empty string if the function is179* anonymous and the object field which it's assigned to is unknown.180*/181webdriver.stacktrace.Frame.prototype.getName = function() {182return this.name_;183};184185186/**187* @return {string} The url or empty string if it is unknown.188*/189webdriver.stacktrace.Frame.prototype.getUrl = function() {190return this.url_;191};192193194/**195* @return {number} The line number if known or -1 if it is unknown.196*/197webdriver.stacktrace.Frame.prototype.getLine = function() {198return this.line_;199};200201202/**203* @return {number} The column number if known and -1 if it is unknown.204*/205webdriver.stacktrace.Frame.prototype.getColumn = function() {206return this.column_;207};208209210/**211* @return {boolean} Whether the stack frame contains an anonymous function.212*/213webdriver.stacktrace.Frame.prototype.isAnonymous = function() {214return !this.name_ || this.context_ == '[object Object]';215};216217218/**219* Converts this frame to its string representation using V8's stack trace220* format: http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi221* @return {string} The string representation of this frame.222* @override223*/224webdriver.stacktrace.Frame.prototype.toString = function() {225var context = this.context_;226if (context && context !== 'new ') {227context += '.';228}229context += this.name_;230context += this.alias_ ? ' [as ' + this.alias_ + ']' : '';231232var path = this.path_ || '<anonymous>';233return ' at ' + (context ? context + ' (' + path + ')' : path);234};235236237/**238* Maximum length of a string that can be matched with a RegExp on239* Firefox 3x. Exceeding this approximate length will cause string.match240* to exceed Firefox's stack quota. This situation can be encountered241* when goog.globalEval is invoked with a long argument; such as242* when loading a module.243* @private {number}244* @const245*/246webdriver.stacktrace.MAX_FIREFOX_FRAMESTRING_LENGTH_ = 500000;247248249/**250* RegExp pattern for JavaScript identifiers. We don't support Unicode251* identifiers defined in ECMAScript v3.252* @private {string}253* @const254*/255webdriver.stacktrace.IDENTIFIER_PATTERN_ = '[a-zA-Z_$][\\w$]*';256257258/**259* Pattern for a matching the type on a fully-qualified name. Forms an260* optional sub-match on the type. For example, in "foo.bar.baz", will match on261* "foo.bar".262* @private {string}263* @const264*/265webdriver.stacktrace.CONTEXT_PATTERN_ =266'(' + webdriver.stacktrace.IDENTIFIER_PATTERN_ +267'(?:\\.' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + ')*)\\.';268269270/**271* Pattern for matching a fully qualified name. Will create two sub-matches:272* the type (optional), and the name. For example, in "foo.bar.baz", will273* match on ["foo.bar", "baz"].274* @private {string}275* @const276*/277webdriver.stacktrace.QUALIFIED_NAME_PATTERN_ =278'(?:' + webdriver.stacktrace.CONTEXT_PATTERN_ + ')?' +279'(' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + ')';280281282/**283* RegExp pattern for function name alias in the V8 stack trace.284* @private {string}285* @const286*/287webdriver.stacktrace.V8_ALIAS_PATTERN_ =288'(?: \\[as (' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + ')\\])?';289290291/**292* RegExp pattern for function names and constructor calls in the V8 stack293* trace.294* @private {string}295* @const296*/297webdriver.stacktrace.V8_FUNCTION_NAME_PATTERN_ =298'(?:' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + '|<anonymous>)';299300301/**302* RegExp pattern for the context of a function call in V8. Creates two303* submatches, only one of which will ever match: either the namespace304* identifier (with optional "new" keyword in the case of a constructor call),305* or just the "new " phrase for a top level constructor call.306* @private {string}307* @const308*/309webdriver.stacktrace.V8_CONTEXT_PATTERN_ =310'(?:((?:new )?(?:\\[object Object\\]|' +311webdriver.stacktrace.IDENTIFIER_PATTERN_ +312'(?:\\.' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + ')*)' +313')\\.|(new ))';314315316/**317* RegExp pattern for function call in the V8 stack trace.318* Creates 3 submatches with context object (optional), function name and319* function alias (optional).320* @private {string}321* @const322*/323webdriver.stacktrace.V8_FUNCTION_CALL_PATTERN_ =324' (?:' + webdriver.stacktrace.V8_CONTEXT_PATTERN_ + ')?' +325'(' + webdriver.stacktrace.V8_FUNCTION_NAME_PATTERN_ + ')' +326webdriver.stacktrace.V8_ALIAS_PATTERN_;327328329/**330* RegExp pattern for an URL + position inside the file.331* @private {string}332* @const333*/334webdriver.stacktrace.URL_PATTERN_ =335'((?:http|https|file)://[^\\s]+|javascript:.*)';336337338/**339* RegExp pattern for a location string in a V8 stack frame. Creates two340* submatches for the location, one for enclosed in parentheticals and on341* where the location appears alone (which will only occur if the location is342* the only information in the frame).343* @private {string}344* @const345* @see http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi346*/347webdriver.stacktrace.V8_LOCATION_PATTERN_ = ' (?:\\((.*)\\)|(.*))';348349350/**351* Regular expression for parsing one stack frame in V8.352* @private {!RegExp}353* @const354*/355webdriver.stacktrace.V8_STACK_FRAME_REGEXP_ = new RegExp('^\\s+at' +356// Prevent intersections with IE10 stack frame regex.357'(?! (?:Anonymous function|Global code|eval code) )' +358'(?:' + webdriver.stacktrace.V8_FUNCTION_CALL_PATTERN_ + ')?' +359webdriver.stacktrace.V8_LOCATION_PATTERN_ + '$');360361362/**363* RegExp pattern for function names in the Firefox stack trace.364* Firefox has extended identifiers to deal with inner functions and anonymous365* functions: https://bugzilla.mozilla.org/show_bug.cgi?id=433529#c9366* @private {string}367* @const368*/369webdriver.stacktrace.FIREFOX_FUNCTION_NAME_PATTERN_ =370webdriver.stacktrace.IDENTIFIER_PATTERN_ + '[\\w./<$]*';371372373/**374* RegExp pattern for function call in the Firefox stack trace.375* Creates a submatch for the function name.376* @private {string}377* @const378*/379webdriver.stacktrace.FIREFOX_FUNCTION_CALL_PATTERN_ =380'(' + webdriver.stacktrace.FIREFOX_FUNCTION_NAME_PATTERN_ + ')?' +381'(?:\\(.*\\))?@';382383384/**385* Regular expression for parsing one stack frame in Firefox.386* @private {!RegExp}387* @const388*/389webdriver.stacktrace.FIREFOX_STACK_FRAME_REGEXP_ = new RegExp('^' +390webdriver.stacktrace.FIREFOX_FUNCTION_CALL_PATTERN_ +391'(?::0|' + webdriver.stacktrace.URL_PATTERN_ + ')$');392393394/**395* RegExp pattern for function call in a Chakra (IE) stack trace. This396* expression creates 2 submatches on the (optional) context and function name,397* matching identifiers like 'foo.Bar.prototype.baz', 'Anonymous function',398* 'eval code', and 'Global code'.399* @private {string}400* @const401*/402webdriver.stacktrace.CHAKRA_FUNCTION_CALL_PATTERN_ =403'(?:(' + webdriver.stacktrace.IDENTIFIER_PATTERN_ +404'(?:\\.' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + ')*)\\.)?' +405'(' + webdriver.stacktrace.IDENTIFIER_PATTERN_ + '(?:\\s+\\w+)*)';406407408/**409* Regular expression for parsing on stack frame in Chakra (IE).410* @private {!RegExp}411* @const412*/413webdriver.stacktrace.CHAKRA_STACK_FRAME_REGEXP_ = new RegExp('^ at ' +414webdriver.stacktrace.CHAKRA_FUNCTION_CALL_PATTERN_ +415'\\s*(?:\\((.*)\\))$');416417418/**419* Placeholder for an unparsable frame in a stack trace generated by420* {@link goog.testing.stacktrace}.421* @private {string}422* @const423*/424webdriver.stacktrace.UNKNOWN_CLOSURE_FRAME_ = '> (unknown)';425426427/**428* Representation of an anonymous frame in a stack trace generated by429* {@link goog.testing.stacktrace}.430* @private {string}431* @const432*/433webdriver.stacktrace.ANONYMOUS_CLOSURE_FRAME_ = '> anonymous';434435436/**437* Pattern for a function call in a Closure stack trace. Creates three optional438* submatches: the context, function name, and alias.439* @private {string}440* @const441*/442webdriver.stacktrace.CLOSURE_FUNCTION_CALL_PATTERN_ =443webdriver.stacktrace.QUALIFIED_NAME_PATTERN_ +444'(?:\\(.*\\))?' + // Ignore arguments if present.445webdriver.stacktrace.V8_ALIAS_PATTERN_;446447448/**449* Regular expression for parsing a stack frame generated by Closure's450* {@link goog.testing.stacktrace}.451* @private {!RegExp}452* @const453*/454webdriver.stacktrace.CLOSURE_STACK_FRAME_REGEXP_ = new RegExp('^> ' +455'(?:' + webdriver.stacktrace.CLOSURE_FUNCTION_CALL_PATTERN_ +456'(?: at )?)?' +457'(?:(.*:\\d+:\\d+)|' + webdriver.stacktrace.URL_PATTERN_ + ')?$');458459460/**461* Parses one stack frame.462* @param {string} frameStr The stack frame as string.463* @return {webdriver.stacktrace.Frame} Stack frame object or null if the464* parsing failed.465* @private466*/467webdriver.stacktrace.parseStackFrame_ = function(frameStr) {468var m = frameStr.match(webdriver.stacktrace.V8_STACK_FRAME_REGEXP_);469if (m) {470return new webdriver.stacktrace.Frame(471m[1] || m[2], m[3], m[4], m[5] || m[6]);472}473474if (frameStr.length >475webdriver.stacktrace.MAX_FIREFOX_FRAMESTRING_LENGTH_) {476return webdriver.stacktrace.parseLongFirefoxFrame_(frameStr);477}478479m = frameStr.match(webdriver.stacktrace.FIREFOX_STACK_FRAME_REGEXP_);480if (m) {481return new webdriver.stacktrace.Frame('', m[1], '', m[2]);482}483484m = frameStr.match(webdriver.stacktrace.CHAKRA_STACK_FRAME_REGEXP_);485if (m) {486return new webdriver.stacktrace.Frame(m[1], m[2], '', m[3]);487}488489if (frameStr == webdriver.stacktrace.UNKNOWN_CLOSURE_FRAME_ ||490frameStr == webdriver.stacktrace.ANONYMOUS_CLOSURE_FRAME_) {491return webdriver.stacktrace.ANONYMOUS_FRAME_;492}493494m = frameStr.match(webdriver.stacktrace.CLOSURE_STACK_FRAME_REGEXP_);495if (m) {496return new webdriver.stacktrace.Frame(m[1], m[2], m[3], m[4] || m[5]);497}498499return null;500};501502503/**504* Parses a long firefox stack frame.505* @param {string} frameStr The stack frame as string.506* @return {!webdriver.stacktrace.Frame} Stack frame object.507* @private508*/509webdriver.stacktrace.parseLongFirefoxFrame_ = function(frameStr) {510var firstParen = frameStr.indexOf('(');511var lastAmpersand = frameStr.lastIndexOf('@');512var lastColon = frameStr.lastIndexOf(':');513var functionName = '';514if ((firstParen >= 0) && (firstParen < lastAmpersand)) {515functionName = frameStr.substring(0, firstParen);516}517var loc = '';518if ((lastAmpersand >= 0) && (lastAmpersand + 1 < lastColon)) {519loc = frameStr.substring(lastAmpersand + 1);520}521return new webdriver.stacktrace.Frame('', functionName, '', loc);522};523524525/**526* Get an error's stack trace with the error string trimmed.527* V8 prepends the string representation of an error to its stack trace.528* This function trims the string so that the stack trace can be parsed529* consistently with the other JS engines.530* @param {(Error|goog.testing.JsUnitException)} error The error.531* @return {string} The stack trace string.532*/533webdriver.stacktrace.getStack = function(error) {534if (!error) {535return '';536}537var stack = error.stack || error.stackTrace || '';538var errorStr = error + '\n';539if (goog.string.startsWith(stack, errorStr)) {540stack = stack.substring(errorStr.length);541}542return stack;543};544545546/**547* Formats an error's stack trace.548* @param {!(Error|goog.testing.JsUnitException)} error The error to format.549* @return {!(Error|goog.testing.JsUnitException)} The formatted error.550*/551webdriver.stacktrace.format = function(error) {552var stack = webdriver.stacktrace.getStack(error);553var frames = webdriver.stacktrace.parse_(stack);554555// If the original stack is in an unexpected format, our formatted stack556// trace will be a bunch of " at <anonymous>" lines. If this is the case,557// just return the error unmodified to avoid losing information. This is558// necessary since the user may have defined a custom stack formatter in559// V8 via Error.prepareStackTrace. See issue 7994.560var isAnonymousFrame = function(frame) {561return frame.toString() === ' at <anonymous>';562};563if (frames.length && goog.array.every(frames, isAnonymousFrame)) {564return error;565}566567// Older versions of IE simply return [object Error] for toString(), so568// only use that as a last resort.569var errorStr = '';570if (error.message) {571errorStr = (error.name ? error.name + ': ' : '') + error.message;572} else {573errorStr = error.toString();574}575576// Ensure the error is in the V8 style with the error's string representation577// prepended to the stack.578error.stack = errorStr + '\n' + frames.join('\n');579return error;580};581582583/**584* Parses an Error object's stack trace.585* @param {string} stack The stack trace.586* @return {!Array.<!webdriver.stacktrace.Frame>} Stack frames. The587* unrecognized frames will be nulled out.588* @private589*/590webdriver.stacktrace.parse_ = function(stack) {591if (!stack) {592return [];593}594595var lines = stack.596replace(/\s*$/, '').597split('\n');598var frames = [];599for (var i = 0; i < lines.length; i++) {600var frame = webdriver.stacktrace.parseStackFrame_(lines[i]);601// The first two frames will be:602// webdriver.stacktrace.Snapshot()603// webdriver.stacktrace.get()604frames.push(frame || webdriver.stacktrace.ANONYMOUS_FRAME_);605}606return frames;607};608609610/**611* Gets the native stack trace if available otherwise follows the call chain.612* The generated trace will exclude all frames up to and including the call to613* this function.614* @return {!Array.<!webdriver.stacktrace.Frame>} The frames of the stack trace.615*/616webdriver.stacktrace.get = function() {617return new webdriver.stacktrace.Snapshot(1).getStacktrace();618};619620621