123456789_123456789_123456789_123456789_123456789_

Class: Selenium::WebDriver::WebSocketConnection

Relationships & Source Files
Inherits: Object
Defined in: rb/lib/selenium/webdriver/common/websocket_connection.rb

Constant Summary

Class Method Summary

Instance Method Summary

Constructor Details

.new(url:) ⇒ WebSocketConnection

[ GitHub ]

  
# File 'rb/lib/selenium/webdriver/common/websocket_connection.rb', line 35

def initialize(url:)
  @callback_threads = ThreadGroup.new

  @session_id = nil
  @url = url

  process_handshake
  @socket_thread = attach_socket_listener
end

Instance Method Details

#add_callback(event, &block)

[ GitHub ]

  
# File 'rb/lib/selenium/webdriver/common/websocket_connection.rb', line 55

def add_callback(event, &block)
  callbacks[event] << block
  block.object_id
end

#attach_socket_listener (private)

[ GitHub ]

  
# File 'rb/lib/selenium/webdriver/common/websocket_connection.rb', line 92

def attach_socket_listener
  Thread.new do
    Thread.current.abort_on_exception = true
    Thread.current.report_on_exception = false

    until socket.eof?
      incoming_frame << socket.readpartial(1024)

      while (frame = incoming_frame.next)
        message = process_frame(frame)
        next unless message['method']

        params = message['params']
        callbacks[message['method']].each do |callback|
          @callback_threads.add(callback_thread(params, &callback))
        end
      end
    end
  rescue *CONNECTION_ERRORS
    Thread.stop
  end
end

#callback_thread(params) (private)

[ GitHub ]

  
# File 'rb/lib/selenium/webdriver/common/websocket_connection.rb', line 132

def callback_thread(params)
  Thread.new do
    Thread.current.abort_on_exception = true

    # We might end up blocked forever when we have an error in event.
    # For example, if network interception event raises error,
    # the browser will keep waiting for the request to be proceeded
    # before returning back to the original thread. In this case,
    # we should at least print the error.
    Thread.current.report_on_exception = true

    yield params
  rescue Error::WebDriverError, *CONNECTION_ERRORS
    Thread.stop
  end
end

#callbacks

[ GitHub ]

  
# File 'rb/lib/selenium/webdriver/common/websocket_connection.rb', line 51

def callbacks
  @callbacks ||= Hash.new { |callbacks, event| callbacks[event] = [] }
end

#close

[ GitHub ]

  
# File 'rb/lib/selenium/webdriver/common/websocket_connection.rb', line 45

def close
  @callback_threads.list.each(&:exit)
  @socket_thread.exit
  socket.close
end

#incoming_frame (private)

[ GitHub ]

  
# File 'rb/lib/selenium/webdriver/common/websocket_connection.rb', line 115

def incoming_frame
  @incoming_frame ||= WebSocket::Frame::Incoming::Client.new(version: ws.version)
end

#messages (private)

We should be thread-safe to use the hash without synchronization because its keys are WebSocket message identifiers and they should be unique within a devtools session.

[ GitHub ]

  
# File 'rb/lib/selenium/webdriver/common/websocket_connection.rb', line 83

def messages
  @messages ||= {}
end

#next_id (private)

[ GitHub ]

  
# File 'rb/lib/selenium/webdriver/common/websocket_connection.rb', line 170

def next_id
  @id ||= 0
  @id += 1
end

#process_frame(frame) (private)

[ GitHub ]

  
# File 'rb/lib/selenium/webdriver/common/websocket_connection.rb', line 119

def process_frame(frame)
  message = frame.to_s

  # Firefox will periodically fail on unparsable empty frame
  return {} if message.empty?

  message = JSON.parse(message)
  messages[message['id']] = message
  WebDriver.logger.debug "WebSocket <- #{message}"[...MAX_LOG_MESSAGE_SIZE], id: :bidi

  message
end

#process_handshake (private)

[ GitHub ]

  
# File 'rb/lib/selenium/webdriver/common/websocket_connection.rb', line 87

def process_handshake
  socket.print(ws.to_s)
  ws << socket.readpartial(1024)
end

#remove_callback(event, id)

[ GitHub ]

  
# File 'rb/lib/selenium/webdriver/common/websocket_connection.rb', line 60

def remove_callback(event, id)
  return if callbacks[event].reject! { |callback| callback.object_id == id }

  ids = callbacks[event]&.map(&:object_id)
  raise Error::WebDriverError, "Callback with ID #{id} does not exist for event #{event}: #{ids}"
end

#send_cmd(**payload)

[ GitHub ]

  
# File 'rb/lib/selenium/webdriver/common/websocket_connection.rb', line 67

def send_cmd(**payload)
  id = next_id
  data = payload.merge(id: id)
  WebDriver.logger.debug "WebSocket -> #{data}"[...MAX_LOG_MESSAGE_SIZE], id: :bidi
  data = JSON.generate(data)
  out_frame = WebSocket::Frame::Outgoing::Client.new(version: ws.version, data: data, type: 'text')
  socket.write(out_frame.to_s)

  wait.until { messages.delete(id) }
end

#socket (private)

[ GitHub ]

  
# File 'rb/lib/selenium/webdriver/common/websocket_connection.rb', line 153

def socket
  @socket ||= if URI(@url).scheme == 'wss'
                socket = TCPSocket.new(ws.host, ws.port)
                socket = OpenSSL::SSL::SSLSocket.new(socket, OpenSSL::SSL::SSLContext.new)
                socket.sync_close = true
                socket.connect

                socket
              else
                TCPSocket.new(ws.host, ws.port)
              end
end

#wait (private)

[ GitHub ]

  
# File 'rb/lib/selenium/webdriver/common/websocket_connection.rb', line 149

def wait
  @wait ||= Wait.new(timeout: RESPONSE_WAIT_TIMEOUT, interval: RESPONSE_WAIT_INTERVAL)
end

#ws (private)

[ GitHub ]

  
# File 'rb/lib/selenium/webdriver/common/websocket_connection.rb', line 166

def ws
  @ws ||= WebSocket::Handshake::Client.new(url: @url)
end