11/* globals Map */
22var inspector = require ( 'inspector' ) ;
33var async = require ( 'async' ) ;
4+ var _ = require ( '../utility' ) ;
5+
6+ // It's helpful to have default limits, as the data expands quickly in real environments.
7+ // depth = 1 is enough to capture the members of top level objects and arrays.
8+ // maxProperties limits the number of properties captured from non-array objects.
9+ // When this value is too small, relevant values for debugging are easily omitted.
10+ // maxArray applies to array objects, which in practice may be arbitrarily large,
11+ // yet for debugging we usually only care about the pattern of data that is established,
12+ // so a smaller limit is usually sufficient.
13+ var DEFAULT_OPTIONS = {
14+ depth : 1 ,
15+ maxProperties : 30 ,
16+ maxArray : 5
17+ }
418
5- function Locals ( config ) {
19+ function Locals ( options ) {
620 if ( ! ( this instanceof Locals ) ) {
7- return new Locals ( config ) ;
21+ return new Locals ( options ) ;
822 }
923
10- this . config = config ;
24+ options = _ . isType ( options , 'object' ) ? options : { } ;
25+ this . options = _ . merge ( DEFAULT_OPTIONS , options ) ;
1126
1227 this . initSession ( ) ;
1328}
@@ -62,7 +77,7 @@ Locals.prototype.mergeLocals = function(localsMap, stack, key, callback) {
6277 return callback ( e ) ;
6378 }
6479
65- getLocalScopesForFrames ( matchedFrames , callback ) ;
80+ getLocalScopesForFrames ( matchedFrames , this . options , callback ) ;
6681}
6782
6883// Finds frames in localParams that match file and line locations in stack.
@@ -115,11 +130,12 @@ function matchedFrame(callFrame, stackLocation) {
115130 callFrameColumn === position . column ;
116131}
117132
118- function getLocalScopesForFrames ( matchedFrames , callback ) {
119- async . each ( matchedFrames , getLocalScopeForFrame , callback ) ;
133+ function getLocalScopesForFrames ( matchedFrames , options , callback ) {
134+ async . each ( matchedFrames , getLocalScopeForFrame . bind ( { options : options } ) , callback ) ;
120135}
121136
122137function getLocalScopeForFrame ( matchedFrame , callback ) {
138+ var options = this . options ;
123139 var scopes = matchedFrame . callFrame . scopeChain ;
124140
125141 var scope = scopes . find ( scope => scope . type === 'local' ) ;
@@ -135,35 +151,102 @@ function getLocalScopeForFrame(matchedFrame, callback) {
135151
136152 var locals = response . result ;
137153 matchedFrame . stackLocation . locals = { } ;
138- for ( var local of locals ) {
139- matchedFrame . stackLocation . locals [ local . name ] = getLocalValue ( local ) ;
154+ var localsContext = {
155+ localsObject : matchedFrame . stackLocation . locals ,
156+ options : options ,
157+ depth : options . depth
140158 }
141-
142- callback ( null ) ;
159+ async . each ( locals , getLocalValue . bind ( localsContext ) , callback ) ;
143160 } ) ;
144161}
145162
146- function getLocalValue ( local ) {
147- var value ;
163+ function getLocalValue ( local , callback ) {
164+ var localsObject = this . localsObject ;
165+ var options = this . options ;
166+ var depth = this . depth ;
167+
168+ function cb ( error , value ) {
169+ if ( error ) {
170+ // Add the relevant data to the error object,
171+ // taking care to preserve the innermost data context.
172+ if ( ! error . rollbarContext ) {
173+ error . rollbarContext = local ;
174+ }
175+ return callback ( error ) ;
176+ }
148177
149- switch ( local . value . type ) {
150- case 'undefined' : value = 'undefined' ; break ;
151- case 'object' : value = getObjectValue ( local ) ; break ;
152- case 'array' : value = getObjectValue ( local ) ; break ;
153- default : value = local . value . value ; break ;
178+ if ( _ . typeName ( localsObject ) === 'array' ) {
179+ localsObject . push ( value ) ;
180+ } else {
181+ localsObject [ local . name ] = value ;
182+ }
183+ callback ( null ) ;
154184 }
155185
156- return value ;
186+ if ( ! local . value ) {
187+ return cb ( null , '[unavailable]' ) ;
188+ }
189+
190+ switch ( local . value . type ) {
191+ case 'undefined' : cb ( null , 'undefined' ) ; break ;
192+ case 'object' : getObjectValue ( local , options , depth , cb ) ; break ;
193+ case 'function' : cb ( null , getObjectType ( local ) ) ; break ;
194+ case 'symbol' : cb ( null , getSymbolValue ( local ) ) ; break ;
195+ default : cb ( null , local . value . value ) ; break ;
196+ }
157197}
158198
159- function getObjectValue ( local ) {
199+ function getObjectType ( local ) {
160200 if ( local . value . className ) {
161- return '<' + local . value . className + ' object>'
201+ return '<' + local . value . className + ' object>' ;
162202 } else {
163- return '<object>'
203+ return '<object>' ;
164204 }
165205}
166206
207+ function getSymbolValue ( local ) {
208+ return local . value . description ;
209+ }
210+
211+ function getObjectValue ( local , options , depth , callback ) {
212+ if ( ! local . value . objectId ) {
213+ if ( 'value' in local . value ) {
214+ // Treat as immediate value. (Known example is `null`.)
215+ return callback ( null , local . value . value ) ;
216+ }
217+ }
218+
219+ if ( depth === 0 ) {
220+ return callback ( null , getObjectType ( local ) ) ;
221+ }
222+
223+ getProperties ( local . value . objectId , function ( err , response ) {
224+ if ( err ) {
225+ return callback ( err ) ;
226+ }
227+
228+ var isArray = local . value . className === 'Array' ;
229+ var length = isArray ? options . maxArray : options . maxProperties ;
230+ var properties = response . result . slice ( 0 , length ) ;
231+ var localsContext = {
232+ localsObject : isArray ? [ ] : { } ,
233+ options : options ,
234+ depth : depth - 1
235+ }
236+
237+ // For arrays, use eachSeries to ensure order is preserved.
238+ // Otherwise, use each for faster completion.
239+ var iterator = isArray ? async . eachSeries : async . each ;
240+ iterator ( properties , getLocalValue . bind ( localsContext ) , function ( error ) {
241+ if ( error ) {
242+ return callback ( error ) ;
243+ }
244+
245+ callback ( null , localsContext . localsObject ) ;
246+ } ) ;
247+ } ) ;
248+ }
249+
167250function getProperties ( objectId , callback ) {
168251 Locals . session . post ( 'Runtime.getProperties' , { objectId : objectId , ownProperties : true } , callback ) ;
169252}
0 commit comments