123456789_123456789_123456789_123456789_123456789_

Module: DEBUGGER__::UI_CDP

Relationships & Source Files
Namespace Children
Modules:
Classes:
Exceptions:
Defined in: lib/debug/server_cdp.rb

Constant Summary

Class Method Summary

Instance Method Summary

Class Method Details

.get_chrome_path

[ GitHub ]

  
# File 'lib/debug/server_cdp.rb', line 66

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

.run_new_chrome

[ GitHub ]

  
# File 'lib/debug/server_cdp.rb', line 82

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

.setup_chrome(addr)

[ GitHub ]

  
# File 'lib/debug/server_cdp.rb', line 16

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

Instance Method Details

#activate_bp(bps)

[ GitHub ]

  
# File 'lib/debug/server_cdp.rb', line 507

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

#cleanup_reader

[ GitHub ]

  
# File 'lib/debug/server_cdp.rb', line 524

def cleanup_reader
  super
  Process.kill :KILL, @chrome_pid if @chrome_pid
end

#deactivate_bp

[ GitHub ]

  
# File 'lib/debug/server_cdp.rb', line 519

def deactivate_bp
  @q_msg << 'del'
  @q_ans << 'y'
end

#del_bp(bps, k)

[ GitHub ]

  
# File 'lib/debug/server_cdp.rb', line 490

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(event, **result)

[ GitHub ]

  
# File 'lib/debug/server_cdp.rb', line 545

def fire_event event, **result
  if result.empty?
    send_event event
  else
    send_event event, **result
  end
end

#get_source_code(path)

[ GitHub ]

  
# File 'lib/debug/server_cdp.rb', line 499

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 330

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

#puts(result)

[ GitHub ]

  
# File 'lib/debug/server_cdp.rb', line 557

def puts result
  # STDERR.puts "puts: #{result}"
  # send_event 'output', category: 'stderr', output: "PUTS!!: " + result.to_s
end

#readline(prompt)

Called by the SESSION thread

[ GitHub ]

  
# File 'lib/debug/server_cdp.rb', line 531

def readline prompt
  return 'c' unless @q_msg

  @q_msg.pop || 'kill!'
end

#respond(req, **result)

[ GitHub ]

  
# File 'lib/debug/server_cdp.rb', line 537

def respond req, **result
  send_response req, **result
end

#respond_fail(req, **result)

[ GitHub ]

  
# File 'lib/debug/server_cdp.rb', line 541

def respond_fail req, **result
  send_fail_response req, **result
end

#send_event(method, **params)

[ GitHub ]

  
# File 'lib/debug/server_cdp.rb', line 320

def send_event method, **params
  if params.empty?
    @ws_server.send method: method, params: {}
  else
    @ws_server.send method: method, params: params
  end
end

#send_fail_response(req, **res)

[ GitHub ]

  
# File 'lib/debug/server_cdp.rb', line 316

def send_fail_response req, **res
  @ws_server.send id: req['id'], error: res
end

#send_response(req, **res)

[ GitHub ]

  
# File 'lib/debug/server_cdp.rb', line 308

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

#sock(skip: false) {|$stderr| ... }

Yields:

  • ($stderr)
[ GitHub ]

  
# File 'lib/debug/server_cdp.rb', line 553

def sock skip: false
  yield $stderr
end