Class: Selenium::WebDriver::WebSocketConnection
Relationships & Source Files | |
Inherits: | Object |
Defined in: | rb/lib/selenium/webdriver/common/websocket_connection.rb |
Constant Summary
-
CONNECTION_ERRORS =
# File 'rb/lib/selenium/webdriver/common/websocket_connection.rb', line 25[ Errno::ECONNRESET, # connection is aborted (browser process was killed) Errno::EPIPE # broken pipe (browser process was killed) ].freeze
-
MAX_LOG_MESSAGE_SIZE =
# File 'rb/lib/selenium/webdriver/common/websocket_connection.rb', line 339999
-
RESPONSE_WAIT_INTERVAL =
# File 'rb/lib/selenium/webdriver/common/websocket_connection.rb', line 310.1
-
RESPONSE_WAIT_TIMEOUT =
# File 'rb/lib/selenium/webdriver/common/websocket_connection.rb', line 3030
Class Method Summary
- .new(url:) ⇒ WebSocketConnection constructor
Instance Method Summary
- #add_callback(event, &block)
- #callbacks
- #close
- #remove_callback(event, id)
- #send_cmd(**payload)
- #attach_socket_listener private
- #callback_thread(params) private
- #incoming_frame private
-
#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.
- #next_id private
- #process_frame(frame) private
- #process_handshake private
- #socket private
- #wait private
- #ws private
Constructor Details
.new(url:) ⇒ WebSocketConnection
# 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) = process_frame(frame) next unless ['method'] params = ['params'] callbacks[ ['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 ]#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.
# File 'rb/lib/selenium/webdriver/common/websocket_connection.rb', line 83
def @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) = frame.to_s # Firefox will periodically fail on unparsable empty frame return {} if .empty? = JSON.parse( ) [ ['id']] = WebDriver.logger.debug "WebSocket <- #{}"[...MAX_LOG_MESSAGE_SIZE], id: :bidi end
#process_handshake (private)
[ GitHub ]#remove_callback(event, id)
# 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 { .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