debug 1.3.4 → 1.6.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.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +234 -9
- data/Gemfile +1 -0
- data/README.md +81 -31
- data/Rakefile +28 -10
- data/debug.gemspec +7 -5
- data/exe/rdbg +7 -3
- data/ext/debug/debug.c +80 -15
- data/ext/debug/extconf.rb +22 -0
- data/lib/debug/breakpoint.rb +141 -67
- data/lib/debug/client.rb +77 -20
- data/lib/debug/color.rb +29 -19
- data/lib/debug/config.rb +61 -27
- data/lib/debug/console.rb +59 -18
- data/lib/debug/frame_info.rb +41 -40
- data/lib/debug/local.rb +1 -1
- data/lib/debug/prelude.rb +2 -2
- data/lib/debug/server.rb +136 -103
- data/lib/debug/server_cdp.rb +880 -162
- data/lib/debug/server_dap.rb +445 -164
- data/lib/debug/session.rb +540 -269
- data/lib/debug/source_repository.rb +103 -52
- data/lib/debug/thread_client.rb +306 -138
- data/lib/debug/tracer.rb +8 -13
- data/lib/debug/version.rb +1 -1
- data/misc/README.md.erb +44 -16
- metadata +6 -15
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -24
- data/.github/ISSUE_TEMPLATE/custom.md +0 -10
- data/.github/ISSUE_TEMPLATE/feature_request.md +0 -14
- data/.github/workflows/ruby.yml +0 -34
- data/.gitignore +0 -12
- data/bin/console +0 -14
- data/bin/gentest +0 -22
- data/bin/setup +0 -8
- data/lib/debug/bp.vim +0 -68
data/lib/debug/server_cdp.rb
CHANGED
@@ -4,23 +4,242 @@ require 'json'
|
|
4
4
|
require 'digest/sha1'
|
5
5
|
require 'base64'
|
6
6
|
require 'securerandom'
|
7
|
+
require 'stringio'
|
8
|
+
require 'open3'
|
9
|
+
require 'tmpdir'
|
7
10
|
|
8
11
|
module DEBUGGER__
|
9
12
|
module UI_CDP
|
10
13
|
SHOW_PROTOCOL = ENV['RUBY_DEBUG_CDP_SHOW_PROTOCOL'] == '1'
|
11
14
|
|
12
|
-
class
|
15
|
+
class << self
|
16
|
+
def setup_chrome addr
|
17
|
+
return if CONFIG[:chrome_path] == ''
|
18
|
+
|
19
|
+
port, path, pid = run_new_chrome
|
20
|
+
begin
|
21
|
+
s = Socket.tcp '127.0.0.1', port
|
22
|
+
rescue Errno::ECONNREFUSED, Errno::EADDRNOTAVAIL
|
23
|
+
return
|
24
|
+
end
|
25
|
+
|
26
|
+
ws_client = WebSocketClient.new(s)
|
27
|
+
ws_client.handshake port, path
|
28
|
+
ws_client.send id: 1, method: 'Target.getTargets'
|
29
|
+
|
30
|
+
loop do
|
31
|
+
res = ws_client.extract_data
|
32
|
+
case
|
33
|
+
when res['id'] == 1 && target_info = res.dig('result', 'targetInfos')
|
34
|
+
page = target_info.find{|t| t['type'] == 'page'}
|
35
|
+
ws_client.send id: 2, method: 'Target.attachToTarget',
|
36
|
+
params: {
|
37
|
+
targetId: page['targetId'],
|
38
|
+
flatten: true
|
39
|
+
}
|
40
|
+
when res['id'] == 2
|
41
|
+
s_id = res.dig('result', 'sessionId')
|
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,
|
52
|
+
method: 'Page.navigate',
|
53
|
+
params: {
|
54
|
+
url: "devtools://devtools/bundled/inspector.html?v8only=true&panel=sources&ws=#{addr}/#{SecureRandom.uuid}",
|
55
|
+
frameId: f_id
|
56
|
+
}
|
57
|
+
when res['method'] == 'Page.loadEventFired'
|
58
|
+
break
|
59
|
+
end
|
60
|
+
end
|
61
|
+
pid
|
62
|
+
rescue Errno::ENOENT
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def get_chrome_path
|
67
|
+
return CONFIG[:chrome_path] if CONFIG[:chrome_path]
|
68
|
+
|
69
|
+
# The process to check OS is based on `selenium` project.
|
70
|
+
case RbConfig::CONFIG['host_os']
|
71
|
+
when /mswin|msys|mingw|cygwin|emc/
|
72
|
+
'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe'
|
73
|
+
when /darwin|mac os/
|
74
|
+
'/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome'
|
75
|
+
when /linux/
|
76
|
+
'google-chrome'
|
77
|
+
else
|
78
|
+
raise "Unsupported OS"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def run_new_chrome
|
83
|
+
dir = Dir.mktmpdir
|
84
|
+
# The command line flags are based on: https://developer.mozilla.org/en-US/docs/Tools/Remote_Debugging/Chrome_Desktop#connecting
|
85
|
+
stdin, stdout, stderr, wait_thr = *Open3.popen3("#{get_chrome_path} --remote-debugging-port=0 --no-first-run --no-default-browser-check --user-data-dir=#{dir}")
|
86
|
+
stdin.close
|
87
|
+
stdout.close
|
88
|
+
|
89
|
+
data = stderr.readpartial 4096
|
90
|
+
if data.match /DevTools listening on ws:\/\/127.0.0.1:(\d+)(.*)/
|
91
|
+
port = $1
|
92
|
+
path = $2
|
93
|
+
end
|
94
|
+
stderr.close
|
95
|
+
|
96
|
+
at_exit{
|
97
|
+
CONFIG[:skip_path] = [//] # skip all
|
98
|
+
FileUtils.rm_rf dir
|
99
|
+
}
|
100
|
+
|
101
|
+
[port, path, wait_thr.pid]
|
102
|
+
end
|
103
|
+
end
|
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
|
+
|
142
|
+
class WebSocketClient
|
143
|
+
include WebSocketUtils
|
144
|
+
|
145
|
+
def initialize s
|
146
|
+
@sock = s
|
147
|
+
end
|
148
|
+
|
149
|
+
def handshake port, path
|
150
|
+
key = SecureRandom.hex(11)
|
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
|
154
|
+
res = @sock.readpartial 4092
|
155
|
+
show_protocol :<, res
|
156
|
+
|
157
|
+
if res.match /^Sec-WebSocket-Accept: (.*)\r\n/
|
158
|
+
correct_key = Base64.strict_encode64 Digest::SHA1.digest "#{key}==258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
159
|
+
raise "The Sec-WebSocket-Accept value: #{$1} is not valid" unless $1 == correct_key
|
160
|
+
else
|
161
|
+
raise "Unknown response: #{res}"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def send **msg
|
166
|
+
msg = JSON.generate(msg)
|
167
|
+
show_protocol :>, msg
|
168
|
+
frame = Frame.new
|
169
|
+
fin = 0b10000000
|
170
|
+
opcode = 0b00000001
|
171
|
+
frame.char fin + opcode
|
172
|
+
|
173
|
+
mask = 0b10000000 # A client must mask all frames in a WebSocket Protocol.
|
174
|
+
bytesize = msg.bytesize
|
175
|
+
if bytesize < 126
|
176
|
+
payload_len = bytesize
|
177
|
+
frame.char mask + payload_len
|
178
|
+
elsif bytesize < 2 ** 16
|
179
|
+
payload_len = 0b01111110
|
180
|
+
frame.char mask + payload_len
|
181
|
+
frame.uint16 bytesize
|
182
|
+
elsif bytesize < 2 ** 64
|
183
|
+
payload_len = 0b01111111
|
184
|
+
frame.char mask + payload_len
|
185
|
+
frame.ulonglong bytesize
|
186
|
+
else
|
187
|
+
raise 'Bytesize is too big.'
|
188
|
+
end
|
189
|
+
|
190
|
+
masking_key = 4.times.map{
|
191
|
+
key = rand(1..255)
|
192
|
+
frame.char key
|
193
|
+
key
|
194
|
+
}
|
195
|
+
msg.bytes.each_with_index do |b, i|
|
196
|
+
frame.char(b ^ masking_key[i % 4])
|
197
|
+
end
|
198
|
+
|
199
|
+
@sock.print frame.b
|
200
|
+
end
|
201
|
+
|
202
|
+
def extract_data
|
203
|
+
first_group = @sock.getbyte
|
204
|
+
fin = first_group & 0b10000000 != 128
|
205
|
+
raise 'Unsupported' if fin
|
206
|
+
opcode = first_group & 0b00001111
|
207
|
+
raise "Unsupported: #{opcode}" unless opcode == 1
|
208
|
+
|
209
|
+
second_group = @sock.getbyte
|
210
|
+
mask = second_group & 0b10000000 == 128
|
211
|
+
raise 'The server must not mask any frames' if mask
|
212
|
+
payload_len = second_group & 0b01111111
|
213
|
+
# TODO: Support other payload_lengths
|
214
|
+
if payload_len == 126
|
215
|
+
payload_len = @sock.read(2).unpack('n*')[0]
|
216
|
+
end
|
217
|
+
|
218
|
+
msg = @sock.read payload_len
|
219
|
+
show_protocol :<, msg
|
220
|
+
JSON.parse msg
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
class Detach < StandardError
|
225
|
+
end
|
226
|
+
|
227
|
+
class WebSocketServer
|
228
|
+
include WebSocketUtils
|
229
|
+
|
13
230
|
def initialize s
|
14
231
|
@sock = s
|
15
232
|
end
|
16
233
|
|
17
|
-
def handshake
|
234
|
+
def handshake
|
18
235
|
req = @sock.readpartial 4096
|
19
|
-
|
20
|
-
|
236
|
+
show_protocol '>', req
|
237
|
+
|
21
238
|
if req.match /^Sec-WebSocket-Key: (.*)\r\n/
|
22
239
|
accept = Base64.strict_encode64 Digest::SHA1.digest "#{$1}258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
23
|
-
|
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
|
24
243
|
else
|
25
244
|
"Unknown request: #{req}"
|
26
245
|
end
|
@@ -28,34 +247,40 @@ module DEBUGGER__
|
|
28
247
|
|
29
248
|
def send **msg
|
30
249
|
msg = JSON.generate(msg)
|
31
|
-
|
250
|
+
show_protocol :<, msg
|
251
|
+
frame = Frame.new
|
32
252
|
fin = 0b10000000
|
33
253
|
opcode = 0b00000001
|
34
|
-
frame
|
35
|
-
|
254
|
+
frame.char fin + opcode
|
255
|
+
|
36
256
|
mask = 0b00000000 # A server must not mask any frames in a WebSocket Protocol.
|
37
257
|
bytesize = msg.bytesize
|
38
258
|
if bytesize < 126
|
39
259
|
payload_len = bytesize
|
260
|
+
frame.char mask + payload_len
|
40
261
|
elsif bytesize < 2 ** 16
|
41
262
|
payload_len = 0b01111110
|
42
|
-
|
43
|
-
|
263
|
+
frame.char mask + payload_len
|
264
|
+
frame.uint16 bytesize
|
265
|
+
elsif bytesize < 2 ** 64
|
44
266
|
payload_len = 0b01111111
|
45
|
-
|
267
|
+
frame.char mask + payload_len
|
268
|
+
frame.ulonglong bytesize
|
269
|
+
else
|
270
|
+
raise 'Bytesize is too big.'
|
46
271
|
end
|
47
|
-
|
48
|
-
frame <<
|
49
|
-
frame.
|
50
|
-
frame.push *msg.bytes
|
51
|
-
@sock.print frame.pack 'c*'
|
272
|
+
|
273
|
+
frame << msg
|
274
|
+
@sock.print frame.b
|
52
275
|
end
|
53
276
|
|
54
277
|
def extract_data
|
55
278
|
first_group = @sock.getbyte
|
56
279
|
fin = first_group & 0b10000000 != 128
|
57
280
|
raise 'Unsupported' if fin
|
281
|
+
|
58
282
|
opcode = first_group & 0b00001111
|
283
|
+
raise Detach if opcode == 8
|
59
284
|
raise "Unsupported: #{opcode}" unless opcode == 1
|
60
285
|
|
61
286
|
second_group = @sock.getbyte
|
@@ -74,138 +299,201 @@ module DEBUGGER__
|
|
74
299
|
masked = @sock.getbyte
|
75
300
|
unmasked << (masked ^ masking_key[n % 4])
|
76
301
|
end
|
77
|
-
|
302
|
+
msg = unmasked.pack 'c*'
|
303
|
+
show_protocol :>, msg
|
304
|
+
JSON.parse msg
|
78
305
|
end
|
79
306
|
end
|
80
307
|
|
81
308
|
def send_response req, **res
|
82
309
|
if res.empty?
|
83
|
-
@
|
310
|
+
@ws_server.send id: req['id'], result: {}
|
84
311
|
else
|
85
|
-
@
|
312
|
+
@ws_server.send id: req['id'], result: res
|
86
313
|
end
|
87
314
|
end
|
88
315
|
|
316
|
+
def send_fail_response req, **res
|
317
|
+
@ws_server.send id: req['id'], error: res
|
318
|
+
end
|
319
|
+
|
89
320
|
def send_event method, **params
|
90
321
|
if params.empty?
|
91
|
-
@
|
322
|
+
@ws_server.send method: method, params: {}
|
92
323
|
else
|
93
|
-
@
|
324
|
+
@ws_server.send method: method, params: params
|
94
325
|
end
|
95
326
|
end
|
96
327
|
|
328
|
+
INVALID_REQUEST = -32600
|
329
|
+
|
97
330
|
def process
|
98
|
-
bps =
|
331
|
+
bps = {}
|
99
332
|
@src_map = {}
|
100
333
|
loop do
|
101
|
-
req = @
|
102
|
-
$stderr.puts '[>]' + req.inspect if SHOW_PROTOCOL
|
334
|
+
req = @ws_server.extract_data
|
103
335
|
|
104
336
|
case req['method']
|
105
337
|
|
106
338
|
## boot/configuration
|
107
|
-
when '
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
id: SecureRandom.hex(16),
|
115
|
-
loaderId: SecureRandom.hex(16),
|
116
|
-
url: 'http://debuggee/',
|
117
|
-
securityOrigin: 'http://debuggee',
|
118
|
-
mimeType: 'text/plain' },
|
119
|
-
resources: [
|
120
|
-
]
|
121
|
-
}
|
122
|
-
send_event 'Debugger.scriptParsed',
|
123
|
-
scriptId: abs,
|
124
|
-
url: "http://debuggee#{abs}",
|
125
|
-
startLine: 0,
|
126
|
-
startColumn: 0,
|
127
|
-
endLine: src.count("\n"),
|
128
|
-
endColumn: 0,
|
129
|
-
executionContextId: 1,
|
130
|
-
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
|
131
346
|
send_event 'Runtime.executionContextCreated',
|
132
347
|
context: {
|
133
348
|
id: SecureRandom.hex(16),
|
134
|
-
origin: "http://#{@
|
349
|
+
origin: "http://#{@local_addr.inspect_sockaddr}",
|
135
350
|
name: ''
|
136
351
|
}
|
137
|
-
when '
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
352
|
+
when 'Runtime.getIsolateId'
|
353
|
+
send_response req,
|
354
|
+
id: SecureRandom.hex
|
355
|
+
when 'Runtime.terminateExecution'
|
356
|
+
send_response req
|
357
|
+
exit
|
142
358
|
when 'Page.startScreencast', 'Emulation.setTouchEmulationEnabled', 'Emulation.setEmitTouchEventsForMouse',
|
143
359
|
'Runtime.compileScript', 'Page.getResourceContent', 'Overlay.setPausedInDebuggerMessage',
|
144
|
-
'
|
360
|
+
'Runtime.releaseObjectGroup', 'Runtime.discardConsoleEntries', 'Log.clear', 'Runtime.runIfWaitingForDebugger'
|
145
361
|
send_response req
|
146
362
|
|
147
363
|
## control
|
148
364
|
when 'Debugger.resume'
|
149
|
-
@q_msg << 'c'
|
150
|
-
@q_msg << req
|
151
365
|
send_response req
|
152
366
|
send_event 'Debugger.resumed'
|
153
|
-
|
154
|
-
@q_msg << 'n'
|
367
|
+
@q_msg << 'c'
|
155
368
|
@q_msg << req
|
156
|
-
|
157
|
-
|
369
|
+
when 'Debugger.stepOver'
|
370
|
+
begin
|
371
|
+
@session.check_postmortem
|
372
|
+
send_response req
|
373
|
+
send_event 'Debugger.resumed'
|
374
|
+
@q_msg << 'n'
|
375
|
+
rescue PostmortemError
|
376
|
+
send_fail_response req,
|
377
|
+
code: INVALID_REQUEST,
|
378
|
+
message: "'stepOver' is not supported while postmortem mode"
|
379
|
+
ensure
|
380
|
+
@q_msg << req
|
381
|
+
end
|
158
382
|
when 'Debugger.stepInto'
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
383
|
+
begin
|
384
|
+
@session.check_postmortem
|
385
|
+
send_response req
|
386
|
+
send_event 'Debugger.resumed'
|
387
|
+
@q_msg << 's'
|
388
|
+
rescue PostmortemError
|
389
|
+
send_fail_response req,
|
390
|
+
code: INVALID_REQUEST,
|
391
|
+
message: "'stepInto' is not supported while postmortem mode"
|
392
|
+
ensure
|
393
|
+
@q_msg << req
|
394
|
+
end
|
163
395
|
when 'Debugger.stepOut'
|
164
|
-
|
165
|
-
|
396
|
+
begin
|
397
|
+
@session.check_postmortem
|
398
|
+
send_response req
|
399
|
+
send_event 'Debugger.resumed'
|
400
|
+
@q_msg << 'fin'
|
401
|
+
rescue PostmortemError
|
402
|
+
send_fail_response req,
|
403
|
+
code: INVALID_REQUEST,
|
404
|
+
message: "'stepOut' is not supported while postmortem mode"
|
405
|
+
ensure
|
406
|
+
@q_msg << req
|
407
|
+
end
|
408
|
+
when 'Debugger.setSkipAllPauses'
|
409
|
+
skip = req.dig('params', 'skip')
|
410
|
+
if skip
|
411
|
+
deactivate_bp
|
412
|
+
else
|
413
|
+
activate_bp bps
|
414
|
+
end
|
166
415
|
send_response req
|
167
|
-
send_event 'Debugger.resumed'
|
168
416
|
|
169
417
|
# breakpoint
|
170
418
|
when 'Debugger.getPossibleBreakpoints'
|
171
|
-
|
172
|
-
line = req.dig('params', 'start', 'lineNumber')
|
173
|
-
src = get_source_code s_id
|
174
|
-
end_line = src.count("\n")
|
175
|
-
line = end_line if line > end_line
|
176
|
-
send_response req,
|
177
|
-
locations: [
|
178
|
-
{ scriptId: s_id,
|
179
|
-
lineNumber: line,
|
180
|
-
}
|
181
|
-
]
|
419
|
+
@q_msg << req
|
182
420
|
when 'Debugger.setBreakpointByUrl'
|
183
421
|
line = req.dig('params', 'lineNumber')
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
422
|
+
if regexp = req.dig('params', 'urlRegex')
|
423
|
+
path = regexp.match(/(.*)\|/)[1].gsub("\\", "")
|
424
|
+
cond = req.dig('params', 'condition')
|
425
|
+
src = get_source_code path
|
426
|
+
end_line = src.lines.count
|
427
|
+
line = end_line if line > end_line
|
428
|
+
b_id = "1:#{line}:#{regexp}"
|
429
|
+
if cond != ''
|
430
|
+
SESSION.add_line_breakpoint(path, line + 1, cond: cond)
|
431
|
+
else
|
432
|
+
SESSION.add_line_breakpoint(path, line + 1)
|
433
|
+
end
|
434
|
+
bps[b_id] = bps.size
|
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: []
|
191
450
|
else
|
192
|
-
|
451
|
+
raise 'Unsupported'
|
193
452
|
end
|
194
|
-
send_response req,
|
195
|
-
breakpointId: (bps.size - 1).to_s,
|
196
|
-
locations: [
|
197
|
-
scriptId: path,
|
198
|
-
lineNumber: line
|
199
|
-
]
|
200
453
|
when 'Debugger.removeBreakpoint'
|
201
454
|
b_id = req.dig('params', 'breakpointId')
|
202
|
-
|
455
|
+
bps = del_bp bps, b_id
|
456
|
+
send_response req
|
457
|
+
when 'Debugger.setBreakpointsActive'
|
458
|
+
active = req.dig('params', 'active')
|
459
|
+
if active
|
460
|
+
activate_bp bps
|
461
|
+
else
|
462
|
+
deactivate_bp # TODO: Change this part because catch breakpoints should not be deactivated.
|
463
|
+
end
|
464
|
+
send_response req
|
465
|
+
when 'Debugger.setPauseOnExceptions'
|
466
|
+
state = req.dig('params', 'state')
|
467
|
+
ex = 'Exception'
|
468
|
+
case state
|
469
|
+
when 'none'
|
470
|
+
@q_msg << 'config postmortem = false'
|
471
|
+
bps = del_bp bps, ex
|
472
|
+
when 'uncaught'
|
473
|
+
@q_msg << 'config postmortem = true'
|
474
|
+
bps = del_bp bps, ex
|
475
|
+
when 'all'
|
476
|
+
@q_msg << 'config postmortem = false'
|
477
|
+
SESSION.add_catch_breakpoint ex
|
478
|
+
bps[ex] = bps.size
|
479
|
+
end
|
203
480
|
send_response req
|
204
481
|
|
205
482
|
when 'Debugger.evaluateOnCallFrame', 'Runtime.getProperties'
|
206
483
|
@q_msg << req
|
207
484
|
end
|
208
485
|
end
|
486
|
+
rescue Detach
|
487
|
+
@q_msg << 'continue'
|
488
|
+
end
|
489
|
+
|
490
|
+
def del_bp bps, k
|
491
|
+
return bps unless idx = bps[k]
|
492
|
+
|
493
|
+
bps.delete k
|
494
|
+
bps.each_key{|i| bps[i] -= 1 if bps[i] > idx}
|
495
|
+
@q_msg << "del #{idx}"
|
496
|
+
bps
|
209
497
|
end
|
210
498
|
|
211
499
|
def get_source_code path
|
@@ -216,9 +504,33 @@ module DEBUGGER__
|
|
216
504
|
src
|
217
505
|
end
|
218
506
|
|
507
|
+
def activate_bp bps
|
508
|
+
bps.each_key{|k|
|
509
|
+
if k.match /^\d+:(\d+):(.*)/
|
510
|
+
line = $1
|
511
|
+
path = $2
|
512
|
+
SESSION.add_line_breakpoint(path, line.to_i + 1)
|
513
|
+
else
|
514
|
+
SESSION.add_catch_breakpoint 'Exception'
|
515
|
+
end
|
516
|
+
}
|
517
|
+
end
|
518
|
+
|
519
|
+
def deactivate_bp
|
520
|
+
@q_msg << 'del'
|
521
|
+
@q_ans << 'y'
|
522
|
+
end
|
523
|
+
|
524
|
+
def cleanup_reader
|
525
|
+
super
|
526
|
+
Process.kill :KILL, @chrome_pid if @chrome_pid
|
527
|
+
end
|
528
|
+
|
219
529
|
## Called by the SESSION thread
|
220
530
|
|
221
531
|
def readline prompt
|
532
|
+
return 'c' unless @q_msg
|
533
|
+
|
222
534
|
@q_msg.pop || 'kill!'
|
223
535
|
end
|
224
536
|
|
@@ -226,6 +538,10 @@ module DEBUGGER__
|
|
226
538
|
send_response req, **result
|
227
539
|
end
|
228
540
|
|
541
|
+
def respond_fail req, **result
|
542
|
+
send_fail_response req, **result
|
543
|
+
end
|
544
|
+
|
229
545
|
def fire_event event, **result
|
230
546
|
if result.empty?
|
231
547
|
send_event event
|
@@ -245,27 +561,95 @@ module DEBUGGER__
|
|
245
561
|
end
|
246
562
|
|
247
563
|
class Session
|
564
|
+
def fail_response req, **result
|
565
|
+
@ui.respond_fail req, **result
|
566
|
+
return :retry
|
567
|
+
end
|
568
|
+
|
569
|
+
INVALID_PARAMS = -32602
|
570
|
+
INTERNAL_ERROR = -32603
|
571
|
+
|
248
572
|
def process_protocol_request req
|
249
573
|
case req['method']
|
250
|
-
when 'Debugger.stepOver', 'Debugger.stepInto', 'Debugger.stepOut', 'Debugger.resume', 'Debugger.
|
251
|
-
|
574
|
+
when 'Debugger.stepOver', 'Debugger.stepInto', 'Debugger.stepOut', 'Debugger.resume', 'Debugger.enable'
|
575
|
+
request_tc [:cdp, :backtrace, req]
|
252
576
|
when 'Debugger.evaluateOnCallFrame'
|
253
|
-
|
254
|
-
|
577
|
+
frame_id = req.dig('params', 'callFrameId')
|
578
|
+
group = req.dig('params', 'objectGroup')
|
579
|
+
if fid = @frame_map[frame_id]
|
580
|
+
expr = req.dig('params', 'expression')
|
581
|
+
request_tc [:cdp, :evaluate, req, fid, expr, group]
|
582
|
+
else
|
583
|
+
fail_response req,
|
584
|
+
code: INVALID_PARAMS,
|
585
|
+
message: "'callFrameId' is an invalid"
|
586
|
+
end
|
255
587
|
when 'Runtime.getProperties'
|
256
588
|
oid = req.dig('params', 'objectId')
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
589
|
+
if ref = @obj_map[oid]
|
590
|
+
case ref[0]
|
591
|
+
when 'local'
|
592
|
+
frame_id = ref[1]
|
593
|
+
fid = @frame_map[frame_id]
|
594
|
+
request_tc [:cdp, :scope, req, fid]
|
595
|
+
when 'properties'
|
596
|
+
request_tc [:cdp, :properties, req, oid]
|
597
|
+
when 'script', 'global'
|
598
|
+
# TODO: Support script and global types
|
599
|
+
@ui.respond req, result: []
|
600
|
+
return :retry
|
601
|
+
else
|
602
|
+
raise "Unknown type: #{ref.inspect}"
|
603
|
+
end
|
604
|
+
else
|
605
|
+
fail_response req,
|
606
|
+
code: INVALID_PARAMS,
|
607
|
+
message: "'objectId' is an invalid"
|
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"
|
268
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
|
269
653
|
end
|
270
654
|
end
|
271
655
|
|
@@ -274,28 +658,115 @@ module DEBUGGER__
|
|
274
658
|
|
275
659
|
case type
|
276
660
|
when :backtrace
|
277
|
-
result[:callFrames].each do |frame|
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
661
|
+
result[:callFrames].each.with_index do |frame, i|
|
662
|
+
frame_id = frame[:callFrameId]
|
663
|
+
@frame_map[frame_id] = i
|
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
|
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
|
689
|
+
|
690
|
+
frame[:scopeChain].each {|s|
|
691
|
+
oid = s.dig(:object, :objectId)
|
692
|
+
@obj_map[oid] = [s[:type], frame_id]
|
693
|
+
}
|
694
|
+
end
|
695
|
+
|
696
|
+
if oid = result.dig(:data, :objectId)
|
697
|
+
@obj_map[oid] = ['properties']
|
698
|
+
end
|
699
|
+
@ui.fire_event 'Debugger.paused', **result
|
700
|
+
when :evaluate
|
701
|
+
message = result.delete :message
|
702
|
+
if message
|
703
|
+
fail_response req,
|
704
|
+
code: INVALID_PARAMS,
|
705
|
+
message: message
|
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',
|
282
712
|
scriptId: s_id,
|
283
|
-
url:
|
713
|
+
url: '',
|
284
714
|
startLine: 0,
|
285
715
|
startColumn: 0,
|
286
|
-
endLine:
|
716
|
+
endLine: lineno,
|
287
717
|
endColumn: 0,
|
288
|
-
executionContextId:
|
289
|
-
hash: src.hash
|
290
|
-
|
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
|
734
|
+
rs = result.dig(:response, :result)
|
735
|
+
[rs].each{|obj|
|
736
|
+
if oid = obj[:objectId]
|
737
|
+
@obj_map[oid] = ['properties']
|
738
|
+
end
|
739
|
+
}
|
740
|
+
@ui.respond req, **result[:response]
|
741
|
+
|
742
|
+
out = result[:output]
|
743
|
+
if out && !out.empty?
|
744
|
+
@ui.fire_event 'Runtime.consoleAPICalled',
|
745
|
+
type: 'log',
|
746
|
+
args: [
|
747
|
+
type: out.class,
|
748
|
+
value: out
|
749
|
+
],
|
750
|
+
executionContextId: 1, # Change this number if something goes wrong.
|
751
|
+
timestamp: Time.now.to_f
|
291
752
|
end
|
292
753
|
end
|
293
|
-
|
294
|
-
|
295
|
-
|
754
|
+
when :scope
|
755
|
+
result.each{|obj|
|
756
|
+
if oid = obj.dig(:value, :objectId)
|
757
|
+
@obj_map[oid] = ['properties']
|
758
|
+
end
|
759
|
+
}
|
296
760
|
@ui.respond req, result: result
|
297
761
|
when :properties
|
298
|
-
|
762
|
+
result.each_value{|v|
|
763
|
+
v.each{|obj|
|
764
|
+
if oid = obj.dig(:value, :objectId)
|
765
|
+
@obj_map[oid] = ['properties']
|
766
|
+
end
|
767
|
+
}
|
768
|
+
}
|
769
|
+
@ui.respond req, **result
|
299
770
|
end
|
300
771
|
end
|
301
772
|
end
|
@@ -307,43 +778,52 @@ module DEBUGGER__
|
|
307
778
|
|
308
779
|
case type
|
309
780
|
when :backtrace
|
310
|
-
|
781
|
+
exception = nil
|
782
|
+
result = {
|
783
|
+
reason: 'other',
|
311
784
|
callFrames: @target_frames.map.with_index{|frame, i|
|
785
|
+
exception = frame.raised_exception if frame == current_frame && frame.has_raised_exception
|
786
|
+
|
312
787
|
path = frame.realpath || frame.path
|
313
|
-
|
314
|
-
|
788
|
+
|
789
|
+
if frame.iseq.nil?
|
790
|
+
lineno = 0
|
315
791
|
else
|
316
|
-
|
792
|
+
lineno = frame.iseq.first_line - 1
|
317
793
|
end
|
318
794
|
|
319
|
-
|
795
|
+
{
|
320
796
|
callFrameId: SecureRandom.hex(16),
|
321
797
|
functionName: frame.name,
|
798
|
+
functionLocation: {
|
799
|
+
# scriptId: N, # filled by SESSION
|
800
|
+
lineNumber: lineno
|
801
|
+
},
|
322
802
|
location: {
|
323
|
-
scriptId:
|
803
|
+
# scriptId: N, # filled by SESSION
|
324
804
|
lineNumber: frame.location.lineno - 1 # The line number is 0-based.
|
325
805
|
},
|
326
|
-
url:
|
806
|
+
url: path,
|
327
807
|
scopeChain: [
|
328
808
|
{
|
329
809
|
type: 'local',
|
330
810
|
object: {
|
331
811
|
type: 'object',
|
332
|
-
objectId:
|
812
|
+
objectId: rand.to_s
|
333
813
|
}
|
334
814
|
},
|
335
815
|
{
|
336
816
|
type: 'script',
|
337
817
|
object: {
|
338
818
|
type: 'object',
|
339
|
-
objectId:
|
819
|
+
objectId: rand.to_s
|
340
820
|
}
|
341
821
|
},
|
342
822
|
{
|
343
823
|
type: 'global',
|
344
824
|
object: {
|
345
825
|
type: 'object',
|
346
|
-
objectId:
|
826
|
+
objectId: rand.to_s
|
347
827
|
}
|
348
828
|
}
|
349
829
|
],
|
@@ -353,15 +833,102 @@ module DEBUGGER__
|
|
353
833
|
}
|
354
834
|
}
|
355
835
|
}
|
836
|
+
|
837
|
+
if exception
|
838
|
+
result[:data] = evaluate_result exception
|
839
|
+
result[:reason] = 'exception'
|
840
|
+
end
|
841
|
+
event! :cdp_result, :backtrace, req, result
|
356
842
|
when :evaluate
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
843
|
+
res = {}
|
844
|
+
fid, expr, group = args
|
845
|
+
frame = @target_frames[fid]
|
846
|
+
message = nil
|
847
|
+
|
848
|
+
if frame && (b = frame.eval_binding)
|
849
|
+
special_local_variables frame do |name, var|
|
850
|
+
b.local_variable_set(name, var) if /\%/ !~name
|
851
|
+
end
|
852
|
+
|
853
|
+
result = nil
|
854
|
+
|
855
|
+
case group
|
856
|
+
when 'popover'
|
857
|
+
case expr
|
858
|
+
# Chrome doesn't read instance variables
|
859
|
+
when /\A\$\S/
|
860
|
+
global_variables.each{|gvar|
|
861
|
+
if gvar.to_s == expr
|
862
|
+
result = eval(gvar.to_s)
|
863
|
+
break false
|
864
|
+
end
|
865
|
+
} and (message = "Error: Not defined global variable: #{expr.inspect}")
|
866
|
+
when /(\A((::[A-Z]|[A-Z])\w*)+)/
|
867
|
+
unless result = search_const(b, $1)
|
868
|
+
message = "Error: Not defined constant: #{expr.inspect}"
|
869
|
+
end
|
870
|
+
else
|
871
|
+
begin
|
872
|
+
result = b.local_variable_get(expr)
|
873
|
+
rescue NameError
|
874
|
+
# try to check method
|
875
|
+
if b.receiver.respond_to? expr, include_all: true
|
876
|
+
result = b.receiver.method(expr)
|
877
|
+
else
|
878
|
+
message = "Error: Can not evaluate: #{expr.inspect}"
|
879
|
+
end
|
880
|
+
end
|
881
|
+
end
|
882
|
+
when 'console', 'watch-group'
|
883
|
+
begin
|
884
|
+
orig_stdout = $stdout
|
885
|
+
$stdout = StringIO.new
|
886
|
+
result = current_frame.binding.eval(expr.to_s, '(DEBUG CONSOLE)')
|
887
|
+
rescue Exception => e
|
888
|
+
result = e
|
889
|
+
b = result.backtrace.map{|e| " #{e}\n"}
|
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
|
908
|
+
res[:exceptionDetails] = {
|
909
|
+
exceptionId: 1,
|
910
|
+
text: 'Uncaught',
|
911
|
+
lineNumber: 0,
|
912
|
+
columnNumber: 0,
|
913
|
+
exception: evaluate_result(result),
|
914
|
+
stackTrace: {
|
915
|
+
callFrames: frames
|
916
|
+
}
|
917
|
+
}
|
918
|
+
ensure
|
919
|
+
output = $stdout.string
|
920
|
+
$stdout = orig_stdout
|
921
|
+
end
|
922
|
+
else
|
923
|
+
message = "Error: unknown objectGroup: #{group}"
|
924
|
+
end
|
925
|
+
else
|
926
|
+
result = Exception.new("Error: Can not evaluate on this frame")
|
362
927
|
end
|
363
|
-
|
364
|
-
|
928
|
+
|
929
|
+
res[:result] = evaluate_result(result)
|
930
|
+
event! :cdp_result, :evaluate, req, message: message, response: res, output: output
|
931
|
+
when :scope
|
365
932
|
fid = args.shift
|
366
933
|
frame = @target_frames[fid]
|
367
934
|
if b = frame.binding
|
@@ -369,8 +936,9 @@ module DEBUGGER__
|
|
369
936
|
v = b.local_variable_get(name)
|
370
937
|
variable(name, v)
|
371
938
|
}
|
372
|
-
|
373
|
-
|
939
|
+
special_local_variables frame do |name, val|
|
940
|
+
vars.unshift variable(name, val)
|
941
|
+
end
|
374
942
|
vars.unshift variable('%self', b.receiver)
|
375
943
|
elsif lvars = frame.local_variables
|
376
944
|
vars = lvars.map{|var, val|
|
@@ -378,48 +946,198 @@ module DEBUGGER__
|
|
378
946
|
}
|
379
947
|
else
|
380
948
|
vars = [variable('%self', frame.self)]
|
381
|
-
|
382
|
-
|
949
|
+
special_local_variables frame do |name, val|
|
950
|
+
vars.unshift variable(name, val)
|
951
|
+
end
|
952
|
+
end
|
953
|
+
event! :cdp_result, :scope, req, vars
|
954
|
+
when :properties
|
955
|
+
oid = args.shift
|
956
|
+
result = []
|
957
|
+
prop = []
|
958
|
+
|
959
|
+
if obj = @obj_map[oid]
|
960
|
+
case obj
|
961
|
+
when Array
|
962
|
+
result = obj.map.with_index{|o, i|
|
963
|
+
variable i.to_s, o
|
964
|
+
}
|
965
|
+
when Hash
|
966
|
+
result = obj.map{|k, v|
|
967
|
+
variable(k, v)
|
968
|
+
}
|
969
|
+
when Struct
|
970
|
+
result = obj.members.map{|m|
|
971
|
+
variable(m, obj[m])
|
972
|
+
}
|
973
|
+
when String
|
974
|
+
prop = [
|
975
|
+
internalProperty('#length', obj.length),
|
976
|
+
internalProperty('#encoding', obj.encoding)
|
977
|
+
]
|
978
|
+
when Class, Module
|
979
|
+
result = obj.instance_variables.map{|iv|
|
980
|
+
variable(iv, obj.instance_variable_get(iv))
|
981
|
+
}
|
982
|
+
prop = [internalProperty('%ancestors', obj.ancestors[1..])]
|
983
|
+
when Range
|
984
|
+
prop = [
|
985
|
+
internalProperty('#begin', obj.begin),
|
986
|
+
internalProperty('#end', obj.end),
|
987
|
+
]
|
988
|
+
end
|
989
|
+
|
990
|
+
result += obj.instance_variables.map{|iv|
|
991
|
+
variable(iv, obj.instance_variable_get(iv))
|
992
|
+
}
|
993
|
+
prop += [internalProperty('#class', obj.class)]
|
383
994
|
end
|
384
|
-
event! :cdp_result, :properties, req,
|
995
|
+
event! :cdp_result, :properties, req, result: result, internalProperties: prop
|
385
996
|
end
|
386
997
|
end
|
387
998
|
|
999
|
+
def search_const b, expr
|
1000
|
+
cs = expr.delete_prefix('::').split('::')
|
1001
|
+
[Object, *b.eval('Module.nesting')].reverse_each{|mod|
|
1002
|
+
if cs.all?{|c|
|
1003
|
+
if mod.const_defined?(c)
|
1004
|
+
mod = mod.const_get(c)
|
1005
|
+
else
|
1006
|
+
false
|
1007
|
+
end
|
1008
|
+
}
|
1009
|
+
# if-body
|
1010
|
+
return mod
|
1011
|
+
end
|
1012
|
+
}
|
1013
|
+
false
|
1014
|
+
end
|
1015
|
+
|
388
1016
|
def evaluate_result r
|
389
1017
|
v = variable nil, r
|
390
1018
|
v[:value]
|
391
1019
|
end
|
392
1020
|
|
393
|
-
def
|
394
|
-
|
1021
|
+
def internalProperty name, obj
|
1022
|
+
v = variable name, obj
|
1023
|
+
v.delete :configurable
|
1024
|
+
v.delete :enumerable
|
1025
|
+
v
|
1026
|
+
end
|
1027
|
+
|
1028
|
+
def propertyDescriptor_ name, obj, type, description: nil, subtype: nil
|
1029
|
+
description = DEBUGGER__.safe_inspect(obj, short: true) if description.nil?
|
1030
|
+
oid = rand.to_s
|
1031
|
+
@obj_map[oid] = obj
|
1032
|
+
prop = {
|
395
1033
|
name: name,
|
396
1034
|
value: {
|
397
1035
|
type: type,
|
398
|
-
|
1036
|
+
description: description,
|
1037
|
+
value: obj,
|
1038
|
+
objectId: oid
|
399
1039
|
},
|
400
|
-
configurable: true,
|
401
|
-
enumerable: true
|
1040
|
+
configurable: true, # TODO: Change these parts because
|
1041
|
+
enumerable: true # they are not necessarily `true`.
|
1042
|
+
}
|
1043
|
+
|
1044
|
+
if type == 'object'
|
1045
|
+
v = prop[:value]
|
1046
|
+
v.delete :value
|
1047
|
+
v[:subtype] = subtype if subtype
|
1048
|
+
v[:className] = obj.class
|
1049
|
+
end
|
1050
|
+
prop
|
1051
|
+
end
|
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
|
+
}
|
402
1067
|
}
|
403
1068
|
end
|
404
1069
|
|
405
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
|
406
1117
|
case obj
|
407
|
-
when Array
|
408
|
-
|
1118
|
+
when Array
|
1119
|
+
propertyDescriptor_ name, obj, 'object', subtype: 'array'
|
1120
|
+
when Hash
|
1121
|
+
propertyDescriptor_ name, obj, 'object', subtype: 'map'
|
409
1122
|
when String
|
410
|
-
|
411
|
-
when Class, Module, Struct
|
412
|
-
variable_ name, obj, 'function'
|
1123
|
+
propertyDescriptor_ name, obj, 'string', description: obj
|
413
1124
|
when TrueClass, FalseClass
|
414
|
-
|
1125
|
+
propertyDescriptor_ name, obj, 'boolean'
|
415
1126
|
when Symbol
|
416
|
-
|
417
|
-
when Float
|
418
|
-
|
419
|
-
when
|
420
|
-
|
1127
|
+
propertyDescriptor_ name, obj, 'symbol'
|
1128
|
+
when Integer, Float
|
1129
|
+
propertyDescriptor_ name, obj, 'number'
|
1130
|
+
when Exception
|
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
|
1137
|
+
end
|
1138
|
+
propertyDescriptor_ name, obj, 'object', description: "#{obj.inspect}\n#{bt}", subtype: 'error'
|
421
1139
|
else
|
422
|
-
|
1140
|
+
propertyDescriptor_ name, obj, 'object'
|
423
1141
|
end
|
424
1142
|
end
|
425
1143
|
end
|