debug 1.4.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -27,7 +27,7 @@ module DEBUGGER__
27
27
  ws_client.handshake port, path
28
28
  ws_client.send id: 1, method: 'Target.getTargets'
29
29
 
30
- 4.times do
30
+ loop do
31
31
  res = ws_client.extract_data
32
32
  case
33
33
  when res['id'] == 1 && target_info = res.dig('result', 'targetInfos')
@@ -39,12 +39,23 @@ module DEBUGGER__
39
39
  }
40
40
  when res['id'] == 2
41
41
  s_id = res.dig('result', 'sessionId')
42
- sleep 0.1
43
- ws_client.send sessionId: s_id, id: 1,
42
+ ws_client.send sessionId: s_id, id: 3,
43
+ method: 'Page.enable'
44
+ when res['id'] == 3
45
+ s_id = res['sessionId']
46
+ ws_client.send sessionId: s_id, id: 4,
47
+ method: 'Page.getFrameTree'
48
+ when res['id'] == 4
49
+ s_id = res['sessionId']
50
+ f_id = res.dig('result', 'frameTree', 'frame', 'id')
51
+ ws_client.send sessionId: s_id, id: 5,
44
52
  method: 'Page.navigate',
45
53
  params: {
46
- url: "devtools://devtools/bundled/inspector.html?ws=#{addr}"
54
+ url: "devtools://devtools/bundled/inspector.html?v8only=true&panel=sources&ws=#{addr}/#{SecureRandom.uuid}",
55
+ frameId: f_id
47
56
  }
57
+ when res['method'] == 'Page.loadEventFired'
58
+ break
48
59
  end
49
60
  end
50
61
  pid
@@ -91,16 +102,57 @@ module DEBUGGER__
91
102
  end
92
103
  end
93
104
 
105
+ module WebSocketUtils
106
+ class Frame
107
+ attr_reader :b
108
+
109
+ def initialize
110
+ @b = ''.b
111
+ end
112
+
113
+ def << obj
114
+ case obj
115
+ when String
116
+ @b << obj.b
117
+ when Enumerable
118
+ obj.each{|e| self << e}
119
+ end
120
+ end
121
+
122
+ def char bytes
123
+ @b << bytes
124
+ end
125
+
126
+ def ulonglong bytes
127
+ @b << [bytes].pack('Q>')
128
+ end
129
+
130
+ def uint16 bytes
131
+ @b << [bytes].pack('n*')
132
+ end
133
+ end
134
+
135
+ def show_protocol dir, msg
136
+ if DEBUGGER__::UI_CDP::SHOW_PROTOCOL
137
+ $stderr.puts "\#[#{dir}] #{msg}"
138
+ end
139
+ end
140
+ end
141
+
94
142
  class WebSocketClient
143
+ include WebSocketUtils
144
+
95
145
  def initialize s
96
146
  @sock = s
97
147
  end
98
148
 
99
149
  def handshake port, path
100
150
  key = SecureRandom.hex(11)
101
- @sock.print "GET #{path} HTTP/1.1\r\nHost: 127.0.0.1:#{port}\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: #{key}==\r\n\r\n"
151
+ req = "GET #{path} HTTP/1.1\r\nHost: 127.0.0.1:#{port}\r\nConnection: Upgrade\r\nUpgrade: websocket\r\nSec-WebSocket-Version: 13\r\nSec-WebSocket-Key: #{key}==\r\n\r\n"
152
+ show_protocol :>, req
153
+ @sock.print req
102
154
  res = @sock.readpartial 4092
103
- $stderr.puts '[>]' + res if SHOW_PROTOCOL
155
+ show_protocol :<, res
104
156
 
105
157
  if res.match /^Sec-WebSocket-Accept: (.*)\r\n/
106
158
  correct_key = Base64.strict_encode64 Digest::SHA1.digest "#{key}==258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
@@ -112,34 +164,39 @@ module DEBUGGER__
112
164
 
113
165
  def send **msg
114
166
  msg = JSON.generate(msg)
115
- frame = []
167
+ show_protocol :>, msg
168
+ frame = Frame.new
116
169
  fin = 0b10000000
117
170
  opcode = 0b00000001
118
- frame << fin + opcode
171
+ frame.char fin + opcode
119
172
 
120
173
  mask = 0b10000000 # A client must mask all frames in a WebSocket Protocol.
121
174
  bytesize = msg.bytesize
122
175
  if bytesize < 126
123
176
  payload_len = bytesize
177
+ frame.char mask + payload_len
124
178
  elsif bytesize < 2 ** 16
125
179
  payload_len = 0b01111110
126
- ex_payload_len = [bytesize].pack('n*').bytes
127
- else
180
+ frame.char mask + payload_len
181
+ frame.uint16 bytesize
182
+ elsif bytesize < 2 ** 64
128
183
  payload_len = 0b01111111
129
- ex_payload_len = [bytesize].pack('Q>').bytes
184
+ frame.char mask + payload_len
185
+ frame.ulonglong bytesize
186
+ else
187
+ raise 'Bytesize is too big.'
130
188
  end
131
189
 
132
- frame << mask + payload_len
133
- frame.push *ex_payload_len if ex_payload_len
134
-
135
- frame.push *masking_key = 4.times.map{rand(1..255)}
136
- masked = []
190
+ masking_key = 4.times.map{
191
+ key = rand(1..255)
192
+ frame.char key
193
+ key
194
+ }
137
195
  msg.bytes.each_with_index do |b, i|
138
- masked << (b ^ masking_key[i % 4])
196
+ frame.char(b ^ masking_key[i % 4])
139
197
  end
140
198
 
141
- frame.push *masked
142
- @sock.print frame.pack 'c*'
199
+ @sock.print frame.b
143
200
  end
144
201
 
145
202
  def extract_data
@@ -158,9 +215,9 @@ module DEBUGGER__
158
215
  payload_len = @sock.read(2).unpack('n*')[0]
159
216
  end
160
217
 
161
- data = JSON.parse @sock.read payload_len
162
- $stderr.puts '[>]' + data.inspect if SHOW_PROTOCOL
163
- data
218
+ msg = @sock.read payload_len
219
+ show_protocol :<, msg
220
+ JSON.parse msg
164
221
  end
165
222
  end
166
223
 
@@ -168,17 +225,21 @@ module DEBUGGER__
168
225
  end
169
226
 
170
227
  class WebSocketServer
228
+ include WebSocketUtils
229
+
171
230
  def initialize s
172
231
  @sock = s
173
232
  end
174
233
 
175
234
  def handshake
176
235
  req = @sock.readpartial 4096
177
- $stderr.puts '[>]' + req if SHOW_PROTOCOL
236
+ show_protocol '>', req
178
237
 
179
238
  if req.match /^Sec-WebSocket-Key: (.*)\r\n/
180
239
  accept = Base64.strict_encode64 Digest::SHA1.digest "#{$1}258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
181
- @sock.print "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: #{accept}\r\n\r\n"
240
+ res = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: #{accept}\r\n\r\n"
241
+ @sock.print res
242
+ show_protocol :<, res
182
243
  else
183
244
  "Unknown request: #{req}"
184
245
  end
@@ -186,27 +247,31 @@ module DEBUGGER__
186
247
 
187
248
  def send **msg
188
249
  msg = JSON.generate(msg)
189
- frame = []
250
+ show_protocol :<, msg
251
+ frame = Frame.new
190
252
  fin = 0b10000000
191
253
  opcode = 0b00000001
192
- frame << fin + opcode
254
+ frame.char fin + opcode
193
255
 
194
256
  mask = 0b00000000 # A server must not mask any frames in a WebSocket Protocol.
195
257
  bytesize = msg.bytesize
196
258
  if bytesize < 126
197
259
  payload_len = bytesize
260
+ frame.char mask + payload_len
198
261
  elsif bytesize < 2 ** 16
199
262
  payload_len = 0b01111110
200
- ex_payload_len = [bytesize].pack('n*').bytes
201
- else
263
+ frame.char mask + payload_len
264
+ frame.uint16 bytesize
265
+ elsif bytesize < 2 ** 64
202
266
  payload_len = 0b01111111
203
- ex_payload_len = [bytesize].pack('Q>').bytes
267
+ frame.char mask + payload_len
268
+ frame.ulonglong bytesize
269
+ else
270
+ raise 'Bytesize is too big.'
204
271
  end
205
272
 
206
- frame << mask + payload_len
207
- frame.push *ex_payload_len if ex_payload_len
208
- frame.push *msg.bytes
209
- @sock.print frame.pack 'c*'
273
+ frame << msg
274
+ @sock.print frame.b
210
275
  end
211
276
 
212
277
  def extract_data
@@ -234,7 +299,9 @@ module DEBUGGER__
234
299
  masked = @sock.getbyte
235
300
  unmasked << (masked ^ masking_key[n % 4])
236
301
  end
237
- JSON.parse unmasked.pack 'c*'
302
+ msg = unmasked.pack 'c*'
303
+ show_protocol :>, msg
304
+ JSON.parse msg
238
305
  end
239
306
  end
240
307
 
@@ -265,63 +332,46 @@ module DEBUGGER__
265
332
  @src_map = {}
266
333
  loop do
267
334
  req = @ws_server.extract_data
268
- $stderr.puts '[>]' + req.inspect if SHOW_PROTOCOL
269
335
 
270
336
  case req['method']
271
337
 
272
338
  ## boot/configuration
273
- when 'Page.getResourceTree'
274
- path = File.absolute_path($0)
275
- src = File.read(path)
276
- @src_map[path] = src
277
- send_response req,
278
- frameTree: {
279
- frame: {
280
- id: SecureRandom.hex(16),
281
- loaderId: SecureRandom.hex(16),
282
- url: 'http://debuggee/',
283
- securityOrigin: 'http://debuggee',
284
- mimeType: 'text/plain' },
285
- resources: [
286
- ]
287
- }
288
- send_event 'Debugger.scriptParsed',
289
- scriptId: path,
290
- url: "http://debuggee#{path}",
291
- startLine: 0,
292
- startColumn: 0,
293
- endLine: src.count("\n"),
294
- endColumn: 0,
295
- executionContextId: 1,
296
- hash: src.hash
339
+ when 'Debugger.getScriptSource'
340
+ @q_msg << req
341
+ when 'Debugger.enable'
342
+ send_response req
343
+ @q_msg << req
344
+ when 'Runtime.enable'
345
+ send_response req
297
346
  send_event 'Runtime.executionContextCreated',
298
347
  context: {
299
348
  id: SecureRandom.hex(16),
300
349
  origin: "http://#{@addr}",
301
350
  name: ''
302
351
  }
303
- when 'Debugger.getScriptSource'
304
- s_id = req.dig('params', 'scriptId')
305
- src = get_source_code s_id
306
- send_response req, scriptSource: src
307
- @q_msg << req
352
+ when 'Runtime.getIsolateId'
353
+ send_response req,
354
+ id: SecureRandom.hex
355
+ when 'Runtime.terminateExecution'
356
+ send_response req
357
+ exit
308
358
  when 'Page.startScreencast', 'Emulation.setTouchEmulationEnabled', 'Emulation.setEmitTouchEventsForMouse',
309
359
  'Runtime.compileScript', 'Page.getResourceContent', 'Overlay.setPausedInDebuggerMessage',
310
- 'Runtime.releaseObjectGroup', 'Runtime.discardConsoleEntries', 'Log.clear'
360
+ 'Runtime.releaseObjectGroup', 'Runtime.discardConsoleEntries', 'Log.clear', 'Runtime.runIfWaitingForDebugger'
311
361
  send_response req
312
362
 
313
363
  ## control
314
364
  when 'Debugger.resume'
315
- @q_msg << 'c'
316
- @q_msg << req
317
365
  send_response req
318
366
  send_event 'Debugger.resumed'
367
+ @q_msg << 'c'
368
+ @q_msg << req
319
369
  when 'Debugger.stepOver'
320
370
  begin
321
371
  @session.check_postmortem
322
- @q_msg << 'n'
323
372
  send_response req
324
373
  send_event 'Debugger.resumed'
374
+ @q_msg << 'n'
325
375
  rescue PostmortemError
326
376
  send_fail_response req,
327
377
  code: INVALID_REQUEST,
@@ -332,9 +382,9 @@ module DEBUGGER__
332
382
  when 'Debugger.stepInto'
333
383
  begin
334
384
  @session.check_postmortem
335
- @q_msg << 's'
336
385
  send_response req
337
386
  send_event 'Debugger.resumed'
387
+ @q_msg << 's'
338
388
  rescue PostmortemError
339
389
  send_fail_response req,
340
390
  code: INVALID_REQUEST,
@@ -345,9 +395,9 @@ module DEBUGGER__
345
395
  when 'Debugger.stepOut'
346
396
  begin
347
397
  @session.check_postmortem
348
- @q_msg << 'fin'
349
398
  send_response req
350
399
  send_event 'Debugger.resumed'
400
+ @q_msg << 'fin'
351
401
  rescue PostmortemError
352
402
  send_fail_response req,
353
403
  code: INVALID_REQUEST,
@@ -366,41 +416,40 @@ module DEBUGGER__
366
416
 
367
417
  # breakpoint
368
418
  when 'Debugger.getPossibleBreakpoints'
369
- s_id = req.dig('params', 'start', 'scriptId')
370
- line = req.dig('params', 'start', 'lineNumber')
371
- src = get_source_code s_id
372
- end_line = src.count("\n")
373
- line = end_line if line > end_line
374
- send_response req,
375
- locations: [
376
- { scriptId: s_id,
377
- lineNumber: line,
378
- }
379
- ]
419
+ @q_msg << req
380
420
  when 'Debugger.setBreakpointByUrl'
381
421
  line = req.dig('params', 'lineNumber')
382
- url = req.dig('params', 'url')
383
- locations = []
384
- if url.match /http:\/\/debuggee(.*)/
385
- path = $1
422
+ if regexp = req.dig('params', 'urlRegex')
423
+ path = regexp.match(/(.*)\|/)[1].gsub("\\", "")
386
424
  cond = req.dig('params', 'condition')
387
425
  src = get_source_code path
388
- end_line = src.count("\n")
426
+ end_line = src.lines.count
389
427
  line = end_line if line > end_line
390
- b_id = "1:#{line}:#{path}"
428
+ b_id = "1:#{line}:#{regexp}"
391
429
  if cond != ''
392
430
  SESSION.add_line_breakpoint(path, line + 1, cond: cond)
393
431
  else
394
432
  SESSION.add_line_breakpoint(path, line + 1)
395
433
  end
396
434
  bps[b_id] = bps.size
397
- locations << {scriptId: path, lineNumber: line}
435
+ # Because we need to return scriptId, responses are returned in SESSION thread.
436
+ req['params']['scriptId'] = path
437
+ req['params']['lineNumber'] = line
438
+ req['params']['breakpointId'] = b_id
439
+ @q_msg << req
440
+ elsif url = req.dig('params', 'url')
441
+ b_id = "#{line}:#{url}"
442
+ send_response req,
443
+ breakpointId: b_id,
444
+ locations: []
445
+ elsif hash = req.dig('params', 'scriptHash')
446
+ b_id = "#{line}:#{hash}"
447
+ send_response req,
448
+ breakpointId: b_id,
449
+ locations: []
398
450
  else
399
- b_id = "1:#{line}:#{url}"
451
+ raise 'Unsupported'
400
452
  end
401
- send_response req,
402
- breakpointId: b_id,
403
- locations: locations
404
453
  when 'Debugger.removeBreakpoint'
405
454
  b_id = req.dig('params', 'breakpointId')
406
455
  bps = del_bp bps, b_id
@@ -473,6 +522,7 @@ module DEBUGGER__
473
522
  end
474
523
 
475
524
  def cleanup_reader
525
+ super
476
526
  Process.kill :KILL, @chrome_pid if @chrome_pid
477
527
  end
478
528
 
@@ -512,21 +562,23 @@ module DEBUGGER__
512
562
 
513
563
  class Session
514
564
  def fail_response req, **result
515
- @ui.respond_fail req, result
565
+ @ui.respond_fail req, **result
516
566
  return :retry
517
567
  end
518
568
 
519
569
  INVALID_PARAMS = -32602
570
+ INTERNAL_ERROR = -32603
520
571
 
521
572
  def process_protocol_request req
522
573
  case req['method']
523
- when 'Debugger.stepOver', 'Debugger.stepInto', 'Debugger.stepOut', 'Debugger.resume', 'Debugger.getScriptSource'
574
+ when 'Debugger.stepOver', 'Debugger.stepInto', 'Debugger.stepOut', 'Debugger.resume', 'Debugger.enable'
524
575
  @tc << [:cdp, :backtrace, req]
525
576
  when 'Debugger.evaluateOnCallFrame'
526
577
  frame_id = req.dig('params', 'callFrameId')
578
+ group = req.dig('params', 'objectGroup')
527
579
  if fid = @frame_map[frame_id]
528
580
  expr = req.dig('params', 'expression')
529
- @tc << [:cdp, :evaluate, req, fid, expr]
581
+ @tc << [:cdp, :evaluate, req, fid, expr, group]
530
582
  else
531
583
  fail_response req,
532
584
  code: INVALID_PARAMS,
@@ -544,7 +596,7 @@ module DEBUGGER__
544
596
  @tc << [:cdp, :properties, req, oid]
545
597
  when 'script', 'global'
546
598
  # TODO: Support script and global types
547
- @ui.respond req
599
+ @ui.respond req, result: []
548
600
  return :retry
549
601
  else
550
602
  raise "Unknown type: #{ref.inspect}"
@@ -554,6 +606,50 @@ module DEBUGGER__
554
606
  code: INVALID_PARAMS,
555
607
  message: "'objectId' is an invalid"
556
608
  end
609
+ when 'Debugger.getScriptSource'
610
+ s_id = req.dig('params', 'scriptId')
611
+ if src = @src_map[s_id]
612
+ @ui.respond req, scriptSource: src
613
+ else
614
+ fail_response req,
615
+ code: INVALID_PARAMS,
616
+ message: "'scriptId' is an invalid"
617
+ end
618
+ return :retry
619
+ when 'Debugger.getPossibleBreakpoints'
620
+ s_id = req.dig('params', 'start', 'scriptId')
621
+ if src = @src_map[s_id]
622
+ lineno = req.dig('params', 'start', 'lineNumber')
623
+ end_line = src.lines.count
624
+ lineno = end_line if lineno > end_line
625
+ @ui.respond req,
626
+ locations: [{
627
+ scriptId: s_id,
628
+ lineNumber: lineno
629
+ }]
630
+ else
631
+ fail_response req,
632
+ code: INVALID_PARAMS,
633
+ message: "'scriptId' is an invalid"
634
+ end
635
+ return :retry
636
+ when 'Debugger.setBreakpointByUrl'
637
+ path = req.dig('params', 'scriptId')
638
+ if s_id = @scr_id_map[path]
639
+ lineno = req.dig('params', 'lineNumber')
640
+ b_id = req.dig('params', 'breakpointId')
641
+ @ui.respond req,
642
+ breakpointId: b_id,
643
+ locations: [{
644
+ scriptId: s_id,
645
+ lineNumber: lineno
646
+ }]
647
+ else
648
+ fail_response req,
649
+ code: INTERNAL_ERROR,
650
+ message: 'The target script is not found...'
651
+ end
652
+ return :retry
557
653
  end
558
654
  end
559
655
 
@@ -565,20 +661,31 @@ module DEBUGGER__
565
661
  result[:callFrames].each.with_index do |frame, i|
566
662
  frame_id = frame[:callFrameId]
567
663
  @frame_map[frame_id] = i
568
- s_id = frame.dig(:location, :scriptId)
569
- if File.exist?(s_id) && !@script_paths.include?(s_id)
570
- src = File.read(s_id)
571
- @ui.fire_event 'Debugger.scriptParsed',
572
- scriptId: s_id,
573
- url: frame[:url],
574
- startLine: 0,
575
- startColumn: 0,
576
- endLine: src.count("\n"),
577
- endColumn: 0,
578
- executionContextId: @script_paths.size + 1,
579
- hash: src.hash
580
- @script_paths << s_id
664
+ path = frame[:url]
665
+ unless s_id = @scr_id_map[path]
666
+ s_id = (@scr_id_map.size + 1).to_s
667
+ @scr_id_map[path] = s_id
668
+ if path && File.exist?(path)
669
+ src = File.read(path)
670
+ end
671
+ @src_map[s_id] = src
581
672
  end
673
+ if src = @src_map[s_id]
674
+ lineno = src.lines.count
675
+ else
676
+ lineno = 0
677
+ end
678
+ frame[:location][:scriptId] = s_id
679
+ frame[:functionLocation][:scriptId] = s_id
680
+ @ui.fire_event 'Debugger.scriptParsed',
681
+ scriptId: s_id,
682
+ url: frame[:url],
683
+ startLine: 0,
684
+ startColumn: 0,
685
+ endLine: lineno,
686
+ endColumn: 0,
687
+ executionContextId: 1,
688
+ hash: src.hash.inspect
582
689
 
583
690
  frame[:scopeChain].each {|s|
584
691
  oid = s.dig(:object, :objectId)
@@ -597,6 +704,33 @@ module DEBUGGER__
597
704
  code: INVALID_PARAMS,
598
705
  message: message
599
706
  else
707
+ src = req.dig('params', 'expression')
708
+ s_id = (@src_map.size + 1).to_s
709
+ @src_map[s_id] = src
710
+ lineno = src.lines.count
711
+ @ui.fire_event 'Debugger.scriptParsed',
712
+ scriptId: s_id,
713
+ url: '',
714
+ startLine: 0,
715
+ startColumn: 0,
716
+ endLine: lineno,
717
+ endColumn: 0,
718
+ executionContextId: 1,
719
+ hash: src.hash.inspect
720
+ if exc = result.dig(:response, :exceptionDetails)
721
+ exc[:stackTrace][:callFrames].each{|frame|
722
+ if frame[:url].empty?
723
+ frame[:scriptId] = s_id
724
+ else
725
+ path = frame[:url]
726
+ unless s_id = @scr_id_map[path]
727
+ s_id = (@scr_id_map.size + 1).to_s
728
+ @scr_id_map[path] = s_id
729
+ end
730
+ frame[:scriptId] = s_id
731
+ end
732
+ }
733
+ end
600
734
  rs = result.dig(:response, :result)
601
735
  [rs].each{|obj|
602
736
  if oid = obj[:objectId]
@@ -651,22 +785,25 @@ module DEBUGGER__
651
785
  exception = frame.raised_exception if frame == current_frame && frame.has_raised_exception
652
786
 
653
787
  path = frame.realpath || frame.path
654
- if path.match /<internal:(.*)>/
655
- path = $1
788
+
789
+ if frame.iseq.nil?
790
+ lineno = 0
791
+ else
792
+ lineno = frame.iseq.first_line - 1
656
793
  end
657
794
 
658
795
  {
659
796
  callFrameId: SecureRandom.hex(16),
660
797
  functionName: frame.name,
661
798
  functionLocation: {
662
- scriptId: path,
663
- lineNumber: 0
799
+ # scriptId: N, # filled by SESSION
800
+ lineNumber: lineno
664
801
  },
665
802
  location: {
666
- scriptId: path,
803
+ # scriptId: N, # filled by SESSION
667
804
  lineNumber: frame.location.lineno - 1 # The line number is 0-based.
668
805
  },
669
- url: "http://debuggee#{path}",
806
+ url: path,
670
807
  scopeChain: [
671
808
  {
672
809
  type: 'local',
@@ -704,19 +841,18 @@ module DEBUGGER__
704
841
  event! :cdp_result, :backtrace, req, result
705
842
  when :evaluate
706
843
  res = {}
707
- fid, expr = args
844
+ fid, expr, group = args
708
845
  frame = @target_frames[fid]
709
846
  message = nil
710
847
 
711
- if frame && (b = frame.binding)
712
- b = b.dup
713
- special_local_variables current_frame do |name, var|
848
+ if frame && (b = frame.eval_binding)
849
+ special_local_variables frame do |name, var|
714
850
  b.local_variable_set(name, var) if /\%/ !~name
715
851
  end
716
852
 
717
853
  result = nil
718
854
 
719
- case req.dig('params', 'objectGroup')
855
+ case group
720
856
  when 'popover'
721
857
  case expr
722
858
  # Chrome doesn't read instance variables
@@ -727,14 +863,12 @@ module DEBUGGER__
727
863
  break false
728
864
  end
729
865
  } and (message = "Error: Not defined global variable: #{expr.inspect}")
730
- when /(\A[A-Z][a-zA-Z]*)/
866
+ when /(\A((::[A-Z]|[A-Z])\w*)+)/
731
867
  unless result = search_const(b, $1)
732
868
  message = "Error: Not defined constant: #{expr.inspect}"
733
869
  end
734
870
  else
735
871
  begin
736
- # try to check local variables
737
- b.local_variable_defined?(expr) or raise NameError
738
872
  result = b.local_variable_get(expr)
739
873
  rescue NameError
740
874
  # try to check method
@@ -745,7 +879,7 @@ module DEBUGGER__
745
879
  end
746
880
  end
747
881
  end
748
- else
882
+ when 'console', 'watch-group'
749
883
  begin
750
884
  orig_stdout = $stdout
751
885
  $stdout = StringIO.new
@@ -753,18 +887,40 @@ module DEBUGGER__
753
887
  rescue Exception => e
754
888
  result = e
755
889
  b = result.backtrace.map{|e| " #{e}\n"}
756
- line = b.first.match('.*:(\d+):in .*')[1].to_i
890
+ frames = [
891
+ {
892
+ columnNumber: 0,
893
+ functionName: 'eval',
894
+ lineNumber: 0,
895
+ url: ''
896
+ }
897
+ ]
898
+ e.backtrace_locations&.each do |loc|
899
+ break if loc.path == __FILE__
900
+ path = loc.absolute_path || loc.path
901
+ frames << {
902
+ columnNumber: 0,
903
+ functionName: loc.base_label,
904
+ lineNumber: loc.lineno - 1,
905
+ url: path
906
+ }
907
+ end
757
908
  res[:exceptionDetails] = {
758
909
  exceptionId: 1,
759
910
  text: 'Uncaught',
760
- lineNumber: line - 1,
911
+ lineNumber: 0,
761
912
  columnNumber: 0,
762
913
  exception: evaluate_result(result),
914
+ stackTrace: {
915
+ callFrames: frames
916
+ }
763
917
  }
764
918
  ensure
765
919
  output = $stdout.string
766
920
  $stdout = orig_stdout
767
921
  end
922
+ else
923
+ message = "Error: unknown objectGroup: #{group}"
768
924
  end
769
925
  else
770
926
  result = Exception.new("Error: Can not evaluate on this frame")
@@ -816,32 +972,32 @@ module DEBUGGER__
816
972
  }
817
973
  when String
818
974
  prop = [
819
- property('#length', obj.length),
820
- property('#encoding', obj.encoding)
975
+ internalProperty('#length', obj.length),
976
+ internalProperty('#encoding', obj.encoding)
821
977
  ]
822
978
  when Class, Module
823
979
  result = obj.instance_variables.map{|iv|
824
980
  variable(iv, obj.instance_variable_get(iv))
825
981
  }
826
- prop = [property('%ancestors', obj.ancestors[1..])]
982
+ prop = [internalProperty('%ancestors', obj.ancestors[1..])]
827
983
  when Range
828
984
  prop = [
829
- property('#begin', obj.begin),
830
- property('#end', obj.end),
985
+ internalProperty('#begin', obj.begin),
986
+ internalProperty('#end', obj.end),
831
987
  ]
832
988
  end
833
989
 
834
990
  result += obj.instance_variables.map{|iv|
835
991
  variable(iv, obj.instance_variable_get(iv))
836
992
  }
837
- prop += [property('#class', obj.class)]
993
+ prop += [internalProperty('#class', obj.class)]
838
994
  end
839
995
  event! :cdp_result, :properties, req, result: result, internalProperties: prop
840
996
  end
841
997
  end
842
998
 
843
999
  def search_const b, expr
844
- cs = expr.split('::')
1000
+ cs = expr.delete_prefix('::').split('::')
845
1001
  [Object, *b.eval('Module.nesting')].reverse_each{|mod|
846
1002
  if cs.all?{|c|
847
1003
  if mod.const_defined?(c)
@@ -862,14 +1018,15 @@ module DEBUGGER__
862
1018
  v[:value]
863
1019
  end
864
1020
 
865
- def property name, obj
1021
+ def internalProperty name, obj
866
1022
  v = variable name, obj
867
1023
  v.delete :configurable
868
1024
  v.delete :enumerable
869
1025
  v
870
1026
  end
871
1027
 
872
- def variable_ name, obj, type, description: obj.inspect, subtype: nil
1028
+ def propertyDescriptor_ name, obj, type, description: nil, subtype: nil
1029
+ description = DEBUGGER__.safe_inspect(obj, short: true) if description.nil?
873
1030
  oid = rand.to_s
874
1031
  @obj_map[oid] = obj
875
1032
  prop = {
@@ -893,30 +1050,94 @@ module DEBUGGER__
893
1050
  prop
894
1051
  end
895
1052
 
1053
+ def preview_ value, hash, overflow
1054
+ {
1055
+ type: value[:type],
1056
+ subtype: value[:subtype],
1057
+ description: value[:description],
1058
+ overflow: overflow,
1059
+ properties: hash.map{|k, v|
1060
+ pd = propertyDescriptor k, v
1061
+ {
1062
+ name: pd[:name],
1063
+ type: pd[:value][:type],
1064
+ value: pd[:value][:description]
1065
+ }
1066
+ }
1067
+ }
1068
+ end
1069
+
896
1070
  def variable name, obj
1071
+ pd = propertyDescriptor name, obj
1072
+ case obj
1073
+ when Array
1074
+ pd[:value][:preview] = preview name, obj
1075
+ obj.each_with_index{|item, idx|
1076
+ if valuePreview = preview(idx.to_s, item)
1077
+ pd[:value][:preview][:properties][idx][:valuePreview] = valuePreview
1078
+ end
1079
+ }
1080
+ when Hash
1081
+ pd[:value][:preview] = preview name, obj
1082
+ obj.each_with_index{|item, idx|
1083
+ key, val = item
1084
+ if valuePreview = preview(key, val)
1085
+ pd[:value][:preview][:properties][idx][:valuePreview] = valuePreview
1086
+ end
1087
+ }
1088
+ end
1089
+ pd
1090
+ end
1091
+
1092
+ def preview name, obj
1093
+ case obj
1094
+ when Array
1095
+ pd = propertyDescriptor name, obj
1096
+ overflow = false
1097
+ if obj.size > 100
1098
+ obj = obj[0..99]
1099
+ overflow = true
1100
+ end
1101
+ hash = obj.each_with_index.to_h{|o, i| [i.to_s, o]}
1102
+ preview_ pd[:value], hash, overflow
1103
+ when Hash
1104
+ pd = propertyDescriptor name, obj
1105
+ overflow = false
1106
+ if obj.size > 100
1107
+ obj = obj.to_a[0..99].to_h
1108
+ overflow = true
1109
+ end
1110
+ preview_ pd[:value], obj, overflow
1111
+ else
1112
+ nil
1113
+ end
1114
+ end
1115
+
1116
+ def propertyDescriptor name, obj
897
1117
  case obj
898
1118
  when Array
899
- variable_ name, obj, 'object', description: "Array(#{obj.size})", subtype: 'array'
1119
+ propertyDescriptor_ name, obj, 'object', subtype: 'array'
900
1120
  when Hash
901
- variable_ name, obj, 'object', description: "Hash(#{obj.size})", subtype: 'map'
1121
+ propertyDescriptor_ name, obj, 'object', subtype: 'map'
902
1122
  when String
903
- variable_ name, obj, 'string', description: obj
904
- when Class, Module, Struct, Range, Time, Method
905
- variable_ name, obj, 'object'
1123
+ propertyDescriptor_ name, obj, 'string', description: obj
906
1124
  when TrueClass, FalseClass
907
- variable_ name, obj, 'boolean'
1125
+ propertyDescriptor_ name, obj, 'boolean'
908
1126
  when Symbol
909
- variable_ name, obj, 'symbol'
1127
+ propertyDescriptor_ name, obj, 'symbol'
910
1128
  when Integer, Float
911
- variable_ name, obj, 'number'
1129
+ propertyDescriptor_ name, obj, 'number'
912
1130
  when Exception
913
- bt = nil
914
- if log = obj.backtrace
915
- bt = log.map{|e| " #{e}\n"}.join
1131
+ bt = ''
1132
+ if log = obj.backtrace_locations
1133
+ log.each do |loc|
1134
+ break if loc.path == __FILE__
1135
+ bt += " #{loc}\n"
1136
+ end
916
1137
  end
917
- variable_ name, obj, 'object', description: "#{obj.inspect}\n#{bt}", subtype: 'error'
1138
+ propertyDescriptor_ name, obj, 'object', description: "#{obj.inspect}\n#{bt}", subtype: 'error'
918
1139
  else
919
- variable_ name, obj, 'undefined'
1140
+ propertyDescriptor_ name, obj, 'object'
920
1141
  end
921
1142
  end
922
1143
  end