%PDF- %PDF-
| Direktori : /var/lib/rbenv/versions/3.1.3/lib64/ruby/gems/3.1.0/gems/debug-1.6.3/lib/debug/ |
| Current File : //var/lib/rbenv/versions/3.1.3/lib64/ruby/gems/3.1.0/gems/debug-1.6.3/lib/debug/server_cdp.rb |
# frozen_string_literal: true
require 'json'
require 'digest/sha1'
require 'base64'
require 'securerandom'
require 'stringio'
require 'open3'
require 'tmpdir'
module DEBUGGER__
module UI_CDP
SHOW_PROTOCOL = ENV['RUBY_DEBUG_CDP_SHOW_PROTOCOL'] == '1'
class << self
def setup_chrome addr
return if CONFIG[:chrome_path] == ''
port, path, pid = run_new_chrome
begin
s = Socket.tcp '127.0.0.1', port
rescue Errno::ECONNREFUSED, Errno::EADDRNOTAVAIL
return
end
ws_client = WebSocketClient.new(s)
ws_client.handshake port, path
ws_client.send id: 1, method: 'Target.getTargets'
loop do
res = ws_client.extract_data
case
when res['id'] == 1 && target_info = res.dig('result', 'targetInfos')
page = target_info.find{|t| t['type'] == 'page'}
ws_client.send id: 2, method: 'Target.attachToTarget',
params: {
targetId: page['targetId'],
flatten: true
}
when res['id'] == 2
s_id = res.dig('result', 'sessionId')
ws_client.send sessionId: s_id, id: 3,
method: 'Page.enable'
when res['id'] == 3
s_id = res['sessionId']
ws_client.send sessionId: s_id, id: 4,
method: 'Page.getFrameTree'
when res['id'] == 4
s_id = res['sessionId']
f_id = res.dig('result', 'frameTree', 'frame', 'id')
ws_client.send sessionId: s_id, id: 5,
method: 'Page.navigate',
params: {
url: "devtools://devtools/bundled/inspector.html?v8only=true&panel=sources&ws=#{addr}/#{SecureRandom.uuid}",
frameId: f_id
}
when res['method'] == 'Page.loadEventFired'
break
end
end
pid
rescue Errno::ENOENT
nil
end
def get_chrome_path
return CONFIG[:chrome_path] if CONFIG[:chrome_path]
# The process to check OS is based on `selenium` project.
case RbConfig::CONFIG['host_os']
when /mswin|msys|mingw|cygwin|emc/
'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe'
when /darwin|mac os/
'/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome'
when /linux/
'google-chrome'
else
raise "Unsupported OS"
end
end
def run_new_chrome
dir = Dir.mktmpdir
# The command line flags are based on: https://developer.mozilla.org/en-US/docs/Tools/Remote_Debugging/Chrome_Desktop#connecting
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}")
stdin.close
stdout.close
data = stderr.readpartial 4096
if data.match /DevTools listening on ws:\/\/127.0.0.1:(\d+)(.*)/
port = $1
path = $2
end
stderr.close
at_exit{
CONFIG[:skip_path] = [//] # skip all
FileUtils.rm_rf dir
}
[port, path, wait_thr.pid]
end
end
module WebSocketUtils
class Frame
attr_reader :b
def initialize
@b = ''.b
end
def << obj
case obj
when String
@b << obj.b
when Enumerable
obj.each{|e| self << e}
end
end
def char bytes
@b << bytes
end
def ulonglong bytes
@b << [bytes].pack('Q>')
end
def uint16 bytes
@b << [bytes].pack('n*')
end
end
def show_protocol dir, msg
if DEBUGGER__::UI_CDP::SHOW_PROTOCOL
$stderr.puts "\#[#{dir}] #{msg}"
end
end
end
class WebSocketClient
include WebSocketUtils
def initialize s
@sock = s
end
def handshake port, path
key = SecureRandom.hex(11)
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"
show_protocol :>, req
@sock.print req
res = @sock.readpartial 4092
show_protocol :<, res
if res.match /^Sec-WebSocket-Accept: (.*)\r\n/
correct_key = Base64.strict_encode64 Digest::SHA1.digest "#{key}==258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
raise "The Sec-WebSocket-Accept value: #{$1} is not valid" unless $1 == correct_key
else
raise "Unknown response: #{res}"
end
end
def send **msg
msg = JSON.generate(msg)
show_protocol :>, msg
frame = Frame.new
fin = 0b10000000
opcode = 0b00000001
frame.char fin + opcode
mask = 0b10000000 # A client must mask all frames in a WebSocket Protocol.
bytesize = msg.bytesize
if bytesize < 126
payload_len = bytesize
frame.char mask + payload_len
elsif bytesize < 2 ** 16
payload_len = 0b01111110
frame.char mask + payload_len
frame.uint16 bytesize
elsif bytesize < 2 ** 64
payload_len = 0b01111111
frame.char mask + payload_len
frame.ulonglong bytesize
else
raise 'Bytesize is too big.'
end
masking_key = 4.times.map{
key = rand(1..255)
frame.char key
key
}
msg.bytes.each_with_index do |b, i|
frame.char(b ^ masking_key[i % 4])
end
@sock.print frame.b
end
def extract_data
first_group = @sock.getbyte
fin = first_group & 0b10000000 != 128
raise 'Unsupported' if fin
opcode = first_group & 0b00001111
raise "Unsupported: #{opcode}" unless opcode == 1
second_group = @sock.getbyte
mask = second_group & 0b10000000 == 128
raise 'The server must not mask any frames' if mask
payload_len = second_group & 0b01111111
# TODO: Support other payload_lengths
if payload_len == 126
payload_len = @sock.read(2).unpack('n*')[0]
end
msg = @sock.read payload_len
show_protocol :<, msg
JSON.parse msg
end
end
class Detach < StandardError
end
class WebSocketServer
include WebSocketUtils
def initialize s
@sock = s
end
def handshake
req = @sock.readpartial 4096
show_protocol '>', req
if req.match /^Sec-WebSocket-Key: (.*)\r\n/
accept = Base64.strict_encode64 Digest::SHA1.digest "#{$1}258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
res = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: #{accept}\r\n\r\n"
@sock.print res
show_protocol :<, res
else
"Unknown request: #{req}"
end
end
def send **msg
msg = JSON.generate(msg)
show_protocol :<, msg
frame = Frame.new
fin = 0b10000000
opcode = 0b00000001
frame.char fin + opcode
mask = 0b00000000 # A server must not mask any frames in a WebSocket Protocol.
bytesize = msg.bytesize
if bytesize < 126
payload_len = bytesize
frame.char mask + payload_len
elsif bytesize < 2 ** 16
payload_len = 0b01111110
frame.char mask + payload_len
frame.uint16 bytesize
elsif bytesize < 2 ** 64
payload_len = 0b01111111
frame.char mask + payload_len
frame.ulonglong bytesize
else
raise 'Bytesize is too big.'
end
frame << msg
@sock.print frame.b
end
def extract_data
first_group = @sock.getbyte
fin = first_group & 0b10000000 != 128
raise 'Unsupported' if fin
opcode = first_group & 0b00001111
raise Detach if opcode == 8
raise "Unsupported: #{opcode}" unless opcode == 1
second_group = @sock.getbyte
mask = second_group & 0b10000000 == 128
raise 'The client must mask all frames' unless mask
payload_len = second_group & 0b01111111
# TODO: Support other payload_lengths
if payload_len == 126
payload_len = @sock.gets(2).unpack('n*')[0]
end
masking_key = []
4.times { masking_key << @sock.getbyte }
unmasked = []
payload_len.times do |n|
masked = @sock.getbyte
unmasked << (masked ^ masking_key[n % 4])
end
msg = unmasked.pack 'c*'
show_protocol :>, msg
JSON.parse msg
end
end
def send_response req, **res
if res.empty?
@ws_server.send id: req['id'], result: {}
else
@ws_server.send id: req['id'], result: res
end
end
def send_fail_response req, **res
@ws_server.send id: req['id'], error: res
end
def send_event method, **params
if params.empty?
@ws_server.send method: method, params: {}
else
@ws_server.send method: method, params: params
end
end
INVALID_REQUEST = -32600
def process
bps = {}
@src_map = {}
loop do
req = @ws_server.extract_data
case req['method']
## boot/configuration
when 'Debugger.getScriptSource'
@q_msg << req
when 'Debugger.enable'
send_response req
@q_msg << req
when 'Runtime.enable'
send_response req
send_event 'Runtime.executionContextCreated',
context: {
id: SecureRandom.hex(16),
origin: "http://#{@local_addr.inspect_sockaddr}",
name: ''
}
when 'Runtime.getIsolateId'
send_response req,
id: SecureRandom.hex
when 'Runtime.terminateExecution'
send_response req
exit
when 'Page.startScreencast', 'Emulation.setTouchEmulationEnabled', 'Emulation.setEmitTouchEventsForMouse',
'Runtime.compileScript', 'Page.getResourceContent', 'Overlay.setPausedInDebuggerMessage',
'Runtime.releaseObjectGroup', 'Runtime.discardConsoleEntries', 'Log.clear', 'Runtime.runIfWaitingForDebugger'
send_response req
## control
when 'Debugger.resume'
send_response req
send_event 'Debugger.resumed'
@q_msg << 'c'
@q_msg << req
when 'Debugger.stepOver'
begin
@session.check_postmortem
send_response req
send_event 'Debugger.resumed'
@q_msg << 'n'
rescue PostmortemError
send_fail_response req,
code: INVALID_REQUEST,
message: "'stepOver' is not supported while postmortem mode"
ensure
@q_msg << req
end
when 'Debugger.stepInto'
begin
@session.check_postmortem
send_response req
send_event 'Debugger.resumed'
@q_msg << 's'
rescue PostmortemError
send_fail_response req,
code: INVALID_REQUEST,
message: "'stepInto' is not supported while postmortem mode"
ensure
@q_msg << req
end
when 'Debugger.stepOut'
begin
@session.check_postmortem
send_response req
send_event 'Debugger.resumed'
@q_msg << 'fin'
rescue PostmortemError
send_fail_response req,
code: INVALID_REQUEST,
message: "'stepOut' is not supported while postmortem mode"
ensure
@q_msg << req
end
when 'Debugger.setSkipAllPauses'
skip = req.dig('params', 'skip')
if skip
deactivate_bp
else
activate_bp bps
end
send_response req
# breakpoint
when 'Debugger.getPossibleBreakpoints'
@q_msg << req
when 'Debugger.setBreakpointByUrl'
line = req.dig('params', 'lineNumber')
if regexp = req.dig('params', 'urlRegex')
path = regexp.match(/(.*)\|/)[1].gsub("\\", "")
cond = req.dig('params', 'condition')
src = get_source_code path
end_line = src.lines.count
line = end_line if line > end_line
b_id = "1:#{line}:#{regexp}"
if cond != ''
SESSION.add_line_breakpoint(path, line + 1, cond: cond)
else
SESSION.add_line_breakpoint(path, line + 1)
end
bps[b_id] = bps.size
# Because we need to return scriptId, responses are returned in SESSION thread.
req['params']['scriptId'] = path
req['params']['lineNumber'] = line
req['params']['breakpointId'] = b_id
@q_msg << req
elsif url = req.dig('params', 'url')
b_id = "#{line}:#{url}"
send_response req,
breakpointId: b_id,
locations: []
elsif hash = req.dig('params', 'scriptHash')
b_id = "#{line}:#{hash}"
send_response req,
breakpointId: b_id,
locations: []
else
raise 'Unsupported'
end
when 'Debugger.removeBreakpoint'
b_id = req.dig('params', 'breakpointId')
bps = del_bp bps, b_id
send_response req
when 'Debugger.setBreakpointsActive'
active = req.dig('params', 'active')
if active
activate_bp bps
else
deactivate_bp # TODO: Change this part because catch breakpoints should not be deactivated.
end
send_response req
when 'Debugger.setPauseOnExceptions'
state = req.dig('params', 'state')
ex = 'Exception'
case state
when 'none'
@q_msg << 'config postmortem = false'
bps = del_bp bps, ex
when 'uncaught'
@q_msg << 'config postmortem = true'
bps = del_bp bps, ex
when 'all'
@q_msg << 'config postmortem = false'
SESSION.add_catch_breakpoint ex
bps[ex] = bps.size
end
send_response req
when 'Debugger.evaluateOnCallFrame', 'Runtime.getProperties'
@q_msg << req
end
end
rescue Detach
@q_msg << 'continue'
end
def del_bp bps, k
return bps unless idx = bps[k]
bps.delete k
bps.each_key{|i| bps[i] -= 1 if bps[i] > idx}
@q_msg << "del #{idx}"
bps
end
def get_source_code path
return @src_map[path] if @src_map[path]
src = File.read(path)
@src_map[path] = src
src
end
def activate_bp bps
bps.each_key{|k|
if k.match /^\d+:(\d+):(.*)/
line = $1
path = $2
SESSION.add_line_breakpoint(path, line.to_i + 1)
else
SESSION.add_catch_breakpoint 'Exception'
end
}
end
def deactivate_bp
@q_msg << 'del'
@q_ans << 'y'
end
def cleanup_reader
super
Process.kill :KILL, @chrome_pid if @chrome_pid
end
## Called by the SESSION thread
def readline prompt
return 'c' unless @q_msg
@q_msg.pop || 'kill!'
end
def respond req, **result
send_response req, **result
end
def respond_fail req, **result
send_fail_response req, **result
end
def fire_event event, **result
if result.empty?
send_event event
else
send_event event, **result
end
end
def sock skip: false
yield $stderr
end
def puts result
# STDERR.puts "puts: #{result}"
# send_event 'output', category: 'stderr', output: "PUTS!!: " + result.to_s
end
end
class Session
def fail_response req, **result
@ui.respond_fail req, **result
return :retry
end
INVALID_PARAMS = -32602
INTERNAL_ERROR = -32603
def process_protocol_request req
case req['method']
when 'Debugger.stepOver', 'Debugger.stepInto', 'Debugger.stepOut', 'Debugger.resume', 'Debugger.enable'
request_tc [:cdp, :backtrace, req]
when 'Debugger.evaluateOnCallFrame'
frame_id = req.dig('params', 'callFrameId')
group = req.dig('params', 'objectGroup')
if fid = @frame_map[frame_id]
expr = req.dig('params', 'expression')
request_tc [:cdp, :evaluate, req, fid, expr, group]
else
fail_response req,
code: INVALID_PARAMS,
message: "'callFrameId' is an invalid"
end
when 'Runtime.getProperties'
oid = req.dig('params', 'objectId')
if ref = @obj_map[oid]
case ref[0]
when 'local'
frame_id = ref[1]
fid = @frame_map[frame_id]
request_tc [:cdp, :scope, req, fid]
when 'properties'
request_tc [:cdp, :properties, req, oid]
when 'script', 'global'
# TODO: Support script and global types
@ui.respond req, result: []
return :retry
else
raise "Unknown type: #{ref.inspect}"
end
else
fail_response req,
code: INVALID_PARAMS,
message: "'objectId' is an invalid"
end
when 'Debugger.getScriptSource'
s_id = req.dig('params', 'scriptId')
if src = @src_map[s_id]
@ui.respond req, scriptSource: src
else
fail_response req,
code: INVALID_PARAMS,
message: "'scriptId' is an invalid"
end
return :retry
when 'Debugger.getPossibleBreakpoints'
s_id = req.dig('params', 'start', 'scriptId')
if src = @src_map[s_id]
lineno = req.dig('params', 'start', 'lineNumber')
end_line = src.lines.count
lineno = end_line if lineno > end_line
@ui.respond req,
locations: [{
scriptId: s_id,
lineNumber: lineno
}]
else
fail_response req,
code: INVALID_PARAMS,
message: "'scriptId' is an invalid"
end
return :retry
when 'Debugger.setBreakpointByUrl'
path = req.dig('params', 'scriptId')
if s_id = @scr_id_map[path]
lineno = req.dig('params', 'lineNumber')
b_id = req.dig('params', 'breakpointId')
@ui.respond req,
breakpointId: b_id,
locations: [{
scriptId: s_id,
lineNumber: lineno
}]
else
fail_response req,
code: INTERNAL_ERROR,
message: 'The target script is not found...'
end
return :retry
end
end
def cdp_event args
type, req, result = args
case type
when :backtrace
result[:callFrames].each.with_index do |frame, i|
frame_id = frame[:callFrameId]
@frame_map[frame_id] = i
path = frame[:url]
unless s_id = @scr_id_map[path]
s_id = (@scr_id_map.size + 1).to_s
@scr_id_map[path] = s_id
if path && File.exist?(path)
src = File.read(path)
end
@src_map[s_id] = src
end
if src = @src_map[s_id]
lineno = src.lines.count
else
lineno = 0
end
frame[:location][:scriptId] = s_id
frame[:functionLocation][:scriptId] = s_id
@ui.fire_event 'Debugger.scriptParsed',
scriptId: s_id,
url: frame[:url],
startLine: 0,
startColumn: 0,
endLine: lineno,
endColumn: 0,
executionContextId: 1,
hash: src.hash.inspect
frame[:scopeChain].each {|s|
oid = s.dig(:object, :objectId)
@obj_map[oid] = [s[:type], frame_id]
}
end
if oid = result.dig(:data, :objectId)
@obj_map[oid] = ['properties']
end
@ui.fire_event 'Debugger.paused', **result
when :evaluate
message = result.delete :message
if message
fail_response req,
code: INVALID_PARAMS,
message: message
else
src = req.dig('params', 'expression')
s_id = (@src_map.size + 1).to_s
@src_map[s_id] = src
lineno = src.lines.count
@ui.fire_event 'Debugger.scriptParsed',
scriptId: s_id,
url: '',
startLine: 0,
startColumn: 0,
endLine: lineno,
endColumn: 0,
executionContextId: 1,
hash: src.hash.inspect
if exc = result.dig(:response, :exceptionDetails)
exc[:stackTrace][:callFrames].each{|frame|
if frame[:url].empty?
frame[:scriptId] = s_id
else
path = frame[:url]
unless s_id = @scr_id_map[path]
s_id = (@scr_id_map.size + 1).to_s
@scr_id_map[path] = s_id
end
frame[:scriptId] = s_id
end
}
end
rs = result.dig(:response, :result)
[rs].each{|obj|
if oid = obj[:objectId]
@obj_map[oid] = ['properties']
end
}
@ui.respond req, **result[:response]
out = result[:output]
if out && !out.empty?
@ui.fire_event 'Runtime.consoleAPICalled',
type: 'log',
args: [
type: out.class,
value: out
],
executionContextId: 1, # Change this number if something goes wrong.
timestamp: Time.now.to_f
end
end
when :scope
result.each{|obj|
if oid = obj.dig(:value, :objectId)
@obj_map[oid] = ['properties']
end
}
@ui.respond req, result: result
when :properties
result.each_value{|v|
v.each{|obj|
if oid = obj.dig(:value, :objectId)
@obj_map[oid] = ['properties']
end
}
}
@ui.respond req, **result
end
end
end
class ThreadClient
def process_cdp args
type = args.shift
req = args.shift
case type
when :backtrace
exception = nil
result = {
reason: 'other',
callFrames: @target_frames.map.with_index{|frame, i|
exception = frame.raised_exception if frame == current_frame && frame.has_raised_exception
path = frame.realpath || frame.path
if frame.iseq.nil?
lineno = 0
else
lineno = frame.iseq.first_line - 1
end
{
callFrameId: SecureRandom.hex(16),
functionName: frame.name,
functionLocation: {
# scriptId: N, # filled by SESSION
lineNumber: lineno
},
location: {
# scriptId: N, # filled by SESSION
lineNumber: frame.location.lineno - 1 # The line number is 0-based.
},
url: path,
scopeChain: [
{
type: 'local',
object: {
type: 'object',
objectId: rand.to_s
}
},
{
type: 'script',
object: {
type: 'object',
objectId: rand.to_s
}
},
{
type: 'global',
object: {
type: 'object',
objectId: rand.to_s
}
}
],
this: {
type: 'object'
}
}
}
}
if exception
result[:data] = evaluate_result exception
result[:reason] = 'exception'
end
event! :cdp_result, :backtrace, req, result
when :evaluate
res = {}
fid, expr, group = args
frame = @target_frames[fid]
message = nil
if frame && (b = frame.eval_binding)
special_local_variables frame do |name, var|
b.local_variable_set(name, var) if /\%/ !~name
end
result = nil
case group
when 'popover'
case expr
# Chrome doesn't read instance variables
when /\A\$\S/
global_variables.each{|gvar|
if gvar.to_s == expr
result = eval(gvar.to_s)
break false
end
} and (message = "Error: Not defined global variable: #{expr.inspect}")
when /(\A((::[A-Z]|[A-Z])\w*)+)/
unless result = search_const(b, $1)
message = "Error: Not defined constant: #{expr.inspect}"
end
else
begin
result = b.local_variable_get(expr)
rescue NameError
# try to check method
if b.receiver.respond_to? expr, include_all: true
result = b.receiver.method(expr)
else
message = "Error: Can not evaluate: #{expr.inspect}"
end
end
end
when 'console', 'watch-group'
begin
orig_stdout = $stdout
$stdout = StringIO.new
result = current_frame.binding.eval(expr.to_s, '(DEBUG CONSOLE)')
rescue Exception => e
result = e
b = result.backtrace.map{|e| " #{e}\n"}
frames = [
{
columnNumber: 0,
functionName: 'eval',
lineNumber: 0,
url: ''
}
]
e.backtrace_locations&.each do |loc|
break if loc.path == __FILE__
path = loc.absolute_path || loc.path
frames << {
columnNumber: 0,
functionName: loc.base_label,
lineNumber: loc.lineno - 1,
url: path
}
end
res[:exceptionDetails] = {
exceptionId: 1,
text: 'Uncaught',
lineNumber: 0,
columnNumber: 0,
exception: evaluate_result(result),
stackTrace: {
callFrames: frames
}
}
ensure
output = $stdout.string
$stdout = orig_stdout
end
else
message = "Error: unknown objectGroup: #{group}"
end
else
result = Exception.new("Error: Can not evaluate on this frame")
end
res[:result] = evaluate_result(result)
event! :cdp_result, :evaluate, req, message: message, response: res, output: output
when :scope
fid = args.shift
frame = @target_frames[fid]
if b = frame.binding
vars = b.local_variables.map{|name|
v = b.local_variable_get(name)
variable(name, v)
}
special_local_variables frame do |name, val|
vars.unshift variable(name, val)
end
vars.unshift variable('%self', b.receiver)
elsif lvars = frame.local_variables
vars = lvars.map{|var, val|
variable(var, val)
}
else
vars = [variable('%self', frame.self)]
special_local_variables frame do |name, val|
vars.unshift variable(name, val)
end
end
event! :cdp_result, :scope, req, vars
when :properties
oid = args.shift
result = []
prop = []
if obj = @obj_map[oid]
case obj
when Array
result = obj.map.with_index{|o, i|
variable i.to_s, o
}
when Hash
result = obj.map{|k, v|
variable(k, v)
}
when Struct
result = obj.members.map{|m|
variable(m, obj[m])
}
when String
prop = [
internalProperty('#length', obj.length),
internalProperty('#encoding', obj.encoding)
]
when Class, Module
result = obj.instance_variables.map{|iv|
variable(iv, obj.instance_variable_get(iv))
}
prop = [internalProperty('%ancestors', obj.ancestors[1..])]
when Range
prop = [
internalProperty('#begin', obj.begin),
internalProperty('#end', obj.end),
]
end
result += obj.instance_variables.map{|iv|
variable(iv, obj.instance_variable_get(iv))
}
prop += [internalProperty('#class', obj.class)]
end
event! :cdp_result, :properties, req, result: result, internalProperties: prop
end
end
def search_const b, expr
cs = expr.delete_prefix('::').split('::')
[Object, *b.eval('::Module.nesting')].reverse_each{|mod|
if cs.all?{|c|
if mod.const_defined?(c)
mod = mod.const_get(c)
else
false
end
}
# if-body
return mod
end
}
false
end
def evaluate_result r
v = variable nil, r
v[:value]
end
def internalProperty name, obj
v = variable name, obj
v.delete :configurable
v.delete :enumerable
v
end
def propertyDescriptor_ name, obj, type, description: nil, subtype: nil
description = DEBUGGER__.safe_inspect(obj, short: true) if description.nil?
oid = rand.to_s
@obj_map[oid] = obj
prop = {
name: name,
value: {
type: type,
description: description,
value: obj,
objectId: oid
},
configurable: true, # TODO: Change these parts because
enumerable: true # they are not necessarily `true`.
}
if type == 'object'
v = prop[:value]
v.delete :value
v[:subtype] = subtype if subtype
v[:className] = obj.class
end
prop
end
def preview_ value, hash, overflow
{
type: value[:type],
subtype: value[:subtype],
description: value[:description],
overflow: overflow,
properties: hash.map{|k, v|
pd = propertyDescriptor k, v
{
name: pd[:name],
type: pd[:value][:type],
value: pd[:value][:description]
}
}
}
end
def variable name, obj
pd = propertyDescriptor name, obj
case obj
when Array
pd[:value][:preview] = preview name, obj
obj.each_with_index{|item, idx|
if valuePreview = preview(idx.to_s, item)
pd[:value][:preview][:properties][idx][:valuePreview] = valuePreview
end
}
when Hash
pd[:value][:preview] = preview name, obj
obj.each_with_index{|item, idx|
key, val = item
if valuePreview = preview(key, val)
pd[:value][:preview][:properties][idx][:valuePreview] = valuePreview
end
}
end
pd
end
def preview name, obj
case obj
when Array
pd = propertyDescriptor name, obj
overflow = false
if obj.size > 100
obj = obj[0..99]
overflow = true
end
hash = obj.each_with_index.to_h{|o, i| [i.to_s, o]}
preview_ pd[:value], hash, overflow
when Hash
pd = propertyDescriptor name, obj
overflow = false
if obj.size > 100
obj = obj.to_a[0..99].to_h
overflow = true
end
preview_ pd[:value], obj, overflow
else
nil
end
end
def propertyDescriptor name, obj
case obj
when Array
propertyDescriptor_ name, obj, 'object', subtype: 'array'
when Hash
propertyDescriptor_ name, obj, 'object', subtype: 'map'
when String
propertyDescriptor_ name, obj, 'string', description: obj
when TrueClass, FalseClass
propertyDescriptor_ name, obj, 'boolean'
when Symbol
propertyDescriptor_ name, obj, 'symbol'
when Integer, Float
propertyDescriptor_ name, obj, 'number'
when Exception
bt = ''
if log = obj.backtrace_locations
log.each do |loc|
break if loc.path == __FILE__
bt += " #{loc}\n"
end
end
propertyDescriptor_ name, obj, 'object', description: "#{obj.inspect}\n#{bt}", subtype: 'error'
else
propertyDescriptor_ name, obj, 'object'
end
end
end
end