Module: DEBUGGER__::UI_CDP
Relationships & Source Files | |
Namespace Children | |
Modules:
| |
Classes:
| |
Exceptions:
| |
Defined in: | lib/debug/server_cdp.rb |
Constant Summary
-
INVALID_REQUEST =
# File 'lib/debug/server_cdp.rb', line 462-32600
-
ITERATIONS =
# File 'lib/debug/server_cdp.rb', line 18550
-
SHOW_PROTOCOL =
# File 'lib/debug/server_cdp.rb', line 14ENV['RUBY_DEBUG_CDP_SHOW_PROTOCOL'] == '1'
-
TIMEOUT_SEC =
# File 'lib/debug/server_cdp.rb', line 845
Class Method Summary
Instance Method Summary
- #activate_bp(bps)
- #add_line_breakpoint(req, b_id, path)
- #cleanup_reader
- #deactivate_bp
- #del_bp(bps, k)
-
#fire_event(method, **params)
Alias for #send_event.
- #get_source_code(path)
- #process
- #puts(result = '')
-
#respond(req, **res)
Called by the SESSION thread.
-
#respond_fail(req, **res)
Alias for #send_fail_response.
- #send_chrome_response(req)
- #send_event(method, **params) (also: #fire_event)
- #send_fail_response(req, **res) (also: #respond_fail)
- #send_http_res(body)
- #send_response(req, **res) (also: #respond)
- #sock(skip: false) {|$stderr| ... }
Class Method Details
.get_chrome_path(candidates)
# File 'lib/debug/server_cdp.rb', line 176
def get_chrome_path candidates candidates.each{|c| if File.exist? c return c end } raise UnsupportedError end
.get_devtools_endpoint(tf)
# File 'lib/debug/server_cdp.rb', line 187
def get_devtools_endpoint tf i = 1 while i < ITERATIONS i += 1 if File.exist?(tf) && data = File.read(tf) if data.match(/DevTools listening on ws:\/\/127.0.0.1:(\d+)(.*)/) port = $1 path = $2 return [port, path] end end sleep 0.1 end raise NotFoundChromeEndpointError end
.run_new_chrome
[ GitHub ]# File 'lib/debug/server_cdp.rb', line 86
def run_new_chrome path = CONFIG[:chrome_path] data = nil port = nil wait_thr = nil # The process to check OS is based on `selenium` project. case RbConfig::CONFIG['host_os'] when /mswin|msys|mingw|cygwin|emc/ if path.nil? candidates = ['C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe', 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'] path = get_chrome_path candidates end # The path is based on https://github.com/sindresorhus/open/blob/v8.4.0/index.js#L128. stdin, stdout, stderr, wait_thr = *Open3.popen3("#{ENV['SystemRoot']}\\System32\\WindowsPowerShell\\v1.0\\powershell") tf = Tempfile.create(['debug-', '.txt']) stdin.puts("Start-process '#{path}' -Argumentlist '--remote-debugging-port=0', '--no-first-run', '--no-default-browser-check', '--user-data-dir=C:\\temp' -Wait -RedirectStandardError #{tf.path}") stdin.close stdout.close stderr.close port, path = get_devtools_endpoint(tf.path) at_exit{ DEBUGGER__.skip_all stdin, stdout, stderr, wait_thr = *Open3.popen3("#{ENV['SystemRoot']}\\System32\\WindowsPowerShell\\v1.0\\powershell") stdin.puts("Stop-process -Name chrome") stdin.close stdout.close stderr.close tf.close begin File.unlink(tf) rescue Errno::EACCES end } when /darwin|mac os/ path = path || '/Applications/Google\ Chrome.app/Contents/MacOS/Google\ 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("#{path} --remote-debugging-port=0 --no-first-run --no-default-browser-check --user-data-dir=#{dir}") stdin.close stdout.close data = stderr.readpartial 4096 stderr.close if data.match(/DevTools listening on ws:\/\/127.0.0.1:(\d+)(.*)/) port = $1 path = $2 end at_exit{ DEBUGGER__.skip_all FileUtils.rm_rf dir } when /linux/ path = path || 'google-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("#{path} --remote-debugging-port=0 --no-first-run --no-default-browser-check --user-data-dir=#{dir}") stdin.close stdout.close data = '' begin Timeout.timeout(TIMEOUT_SEC) do until data.match?(/DevTools listening on ws:\/\/127.0.0.1:\d+.*/) data = stderr.readpartial 4096 end end rescue Exception raise NotFoundChromeEndpointError end stderr.close if data.match(/DevTools listening on ws:\/\/127.0.0.1:(\d+)(.*)/) port = $1 path = $2 end at_exit{ DEBUGGER__.skip_all FileUtils.rm_rf dir } else raise UnsupportedError end [port, path, wait_thr.pid] end
.setup_chrome(addr, uuid)
[ GitHub ]# File 'lib/debug/server_cdp.rb', line 20
def setup_chrome addr, uuid 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 res['id'] when 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 2 s_id = res.dig('result', 'sessionId') # TODO: change id ws_client.send sessionId: s_id, id: 100, method: 'Network.enable' ws_client.send sessionId: s_id, id: 3, method: 'Page.enable' when 3 s_id = res['sessionId'] ws_client.send sessionId: s_id, id: 4, method: 'Page.getFrameTree' when 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&noJavaScriptCompletion=true&ws=#{addr}/#{uuid}", frameId: f_id } when 101 break else if res['method'] == 'Network.webSocketWillSendHandshakeRequest' s_id = res['sessionId'] # Display the console by entering ESC key ws_client.send sessionId: s_id, id: 101, # TODO: change id method:"Input.dispatchKeyEvent", params: { type:"keyDown", windowsVirtualKeyCode:27 # ESC key } end end end pid rescue Errno::ENOENT, UnsupportedError, NotFoundChromeEndpointError nil end
Instance Method Details
#activate_bp(bps)
[ GitHub ]# File 'lib/debug/server_cdp.rb', line 658
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
#add_line_breakpoint(req, b_id, path)
[ GitHub ]# File 'lib/debug/server_cdp.rb', line 623
def add_line_breakpoint req, b_id, path cond = req.dig('params', 'condition') line = req.dig('params', 'lineNumber') src = get_source_code path end_line = src.lines.count line = end_line if line > end_line if cond != '' SESSION.add_line_breakpoint(path, line + 1, cond: cond) else SESSION.add_line_breakpoint(path, line + 1) end # 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 end
#cleanup_reader
[ GitHub ]# File 'lib/debug/server_cdp.rb', line 675
def cleanup_reader super Process.kill :KILL, @chrome_pid if @chrome_pid rescue Errno::ESRCH # continue if @chrome_pid process is not found end
#deactivate_bp
[ GitHub ]# File 'lib/debug/server_cdp.rb', line 670
def deactivate_bp @q_msg << 'del' @q_ans << 'y' end
#del_bp(bps, k)
[ GitHub ]# File 'lib/debug/server_cdp.rb', line 641
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
#fire_event(method, **params)
Alias for #send_event.
# File 'lib/debug/server_cdp.rb', line 685
alias fire_event send_event
#get_source_code(path)
[ GitHub ]# File 'lib/debug/server_cdp.rb', line 650
def get_source_code path return @src_map[path] if @src_map[path] src = File.read(path) @src_map[path] = src src end
#process
[ GitHub ]# File 'lib/debug/server_cdp.rb', line 464
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, debuggerId: rand.to_s @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 when 'Debugger.pause' send_response req Process.kill(UI_ServerBase::TRAP_SIGNAL, Process.pid) # breakpoint when 'Debugger.getPossibleBreakpoints' @q_msg << req when 'Debugger.setBreakpointByUrl' line = req.dig('params', 'lineNumber') if regexp = req.dig('params', 'urlRegex') b_id = "1:#{line}:#{regexp}" bps[b_id] = bps.size path = regexp.match(/(.*)\|/)[1].gsub("\\", "") add_line_breakpoint(req, b_id, path) elsif url = req.dig('params', 'url') b_id = "#{line}:#{url}" # When breakpoints are set in Script snippet, non-existent path such as "snippet:///Script%20snippet%20%231" sent. # That's why we need to check it here. if File.exist? url bps[b_id] = bps.size add_line_breakpoint(req, b_id, url) else send_response req, breakpointId: b_id, locations: [] end else if hash = req.dig('params', 'scriptHash') b_id = "#{line}:#{hash}" send_response req, breakpointId: b_id, locations: [] else raise 'Unsupported' end 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
#puts(result = '')
[ GitHub ]# File 'lib/debug/server_cdp.rb', line 691
def puts result='' # STDERR.puts "puts: #{result}" # send_event 'output', category: 'stderr', output: "PUTS!!: " + result.to_s end
#respond(req, **res)
Called by the SESSION thread
# File 'lib/debug/server_cdp.rb', line 683
alias respond send_response
#respond_fail(req, **res)
Alias for #send_fail_response.
# File 'lib/debug/server_cdp.rb', line 684
alias respond_fail send_fail_response
#send_chrome_response(req)
[ GitHub ]# File 'lib/debug/server_cdp.rb', line 204
def send_chrome_response req @repl = false case req when /^GET\s\/json\/version\sHTTP\/1.1/ body = { Browser: "ruby/v#{RUBY_VERSION}", 'Protocol-Version': "1.1" } send_http_res body raise UI_ServerBase::RetryConnection when /^GET\s\/json\sHTTP\/1.1/ @uuid = @uuid || SecureRandom.uuid addr = @local_addr.inspect_sockaddr body = [{ description: "ruby instance", devtoolsFrontendUrl: "devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=#{addr}/#{@uuid}", id: @uuid, title: $0, type: "node", url: "file://#{File.absolute_path($0)}", webSocketDebuggerUrl: "ws://#{addr}/#{@uuid}" }] send_http_res body raise UI_ServerBase::RetryConnection when /^GET\s\/(\w{8}-\w{4}-\w{4}-\w{4}-\w{12})\sHTTP\/1.1/ raise 'Incorrect uuid' unless $1 == @uuid @need_pause_at_first = false CONFIG.set_config no_color: true @ws_server = WebSocketServer.new(@sock) @ws_server.handshake end end
#send_event(method, **params) Also known as: #fire_event
[ GitHub ]# File 'lib/debug/server_cdp.rb', line 458
def send_event method, **params @ws_server.send method: method, params: params end
#send_fail_response(req, **res) Also known as: #respond_fail
[ GitHub ]# File 'lib/debug/server_cdp.rb', line 454
def send_fail_response req, **res @ws_server.send id: req['id'], error: res end
#send_http_res(body)
[ GitHub ]# File 'lib/debug/server_cdp.rb', line 241
def send_http_res body json = JSON.generate body header = "HTTP/1.0 200 OK\r\nContent-Type: application/json; charset=UTF-8\r\nCache-Control: no-cache\r\nContent-Length: #{json.bytesize}\r\n\r\n" @sock.puts "#{header}#{json}" end
#send_response(req, **res) Also known as: #respond
[ GitHub ]# File 'lib/debug/server_cdp.rb', line 450
def send_response req, **res @ws_server.send id: req['id'], result: res end
#sock(skip: false) {|$stderr| ... }
# File 'lib/debug/server_cdp.rb', line 687
def sock skip: false yield $stderr end