debug 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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