1
1
/* globals Map */
2
2
var inspector = require ( 'inspector' ) ;
3
3
var 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
+ }
4
18
5
- function Locals ( config ) {
19
+ function Locals ( options ) {
6
20
if ( ! ( this instanceof Locals ) ) {
7
- return new Locals ( config ) ;
21
+ return new Locals ( options ) ;
8
22
}
9
23
10
- this . config = config ;
24
+ options = _ . isType ( options , 'object' ) ? options : { } ;
25
+ this . options = _ . merge ( DEFAULT_OPTIONS , options ) ;
11
26
12
27
this . initSession ( ) ;
13
28
}
@@ -62,7 +77,7 @@ Locals.prototype.mergeLocals = function(localsMap, stack, key, callback) {
62
77
return callback ( e ) ;
63
78
}
64
79
65
- getLocalScopesForFrames ( matchedFrames , callback ) ;
80
+ getLocalScopesForFrames ( matchedFrames , this . options , callback ) ;
66
81
}
67
82
68
83
// Finds frames in localParams that match file and line locations in stack.
@@ -115,11 +130,12 @@ function matchedFrame(callFrame, stackLocation) {
115
130
callFrameColumn === position . column ;
116
131
}
117
132
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 ) ;
120
135
}
121
136
122
137
function getLocalScopeForFrame ( matchedFrame , callback ) {
138
+ var options = this . options ;
123
139
var scopes = matchedFrame . callFrame . scopeChain ;
124
140
125
141
var scope = scopes . find ( scope => scope . type === 'local' ) ;
@@ -135,35 +151,102 @@ function getLocalScopeForFrame(matchedFrame, callback) {
135
151
136
152
var locals = response . result ;
137
153
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
140
158
}
141
-
142
- callback ( null ) ;
159
+ async . each ( locals , getLocalValue . bind ( localsContext ) , callback ) ;
143
160
} ) ;
144
161
}
145
162
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
+ }
148
177
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 ) ;
154
184
}
155
185
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
+ }
157
197
}
158
198
159
- function getObjectValue ( local ) {
199
+ function getObjectType ( local ) {
160
200
if ( local . value . className ) {
161
- return '<' + local . value . className + ' object>'
201
+ return '<' + local . value . className + ' object>' ;
162
202
} else {
163
- return '<object>'
203
+ return '<object>' ;
164
204
}
165
205
}
166
206
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
+
167
250
function getProperties ( objectId , callback ) {
168
251
Locals . session . post ( 'Runtime.getProperties' , { objectId : objectId , ownProperties : true } , callback ) ;
169
252
}
0 commit comments