Class: Selenium::WebDriver::WebSocketConnection

Inherits:
Object
  • Object
show all
Defined in:
lib/selenium/webdriver/common/websocket_connection.rb

Constant Summary collapse

CONNECTION_ERRORS =
[
  Errno::ECONNRESET, # connection is aborted (browser process was killed)
  Errno::EPIPE, # broken pipe (browser process was killed)
  Errno::EBADF, # file descriptor already closed (double-close or GC)
  IOError, # Ruby socket read/write after close
  EOFError # socket reached EOF after remote closed cleanly
].freeze
RESPONSE_WAIT_TIMEOUT =
30
RESPONSE_WAIT_INTERVAL =
0.1
MAX_LOG_MESSAGE_SIZE =
9999

Instance Method Summary collapse

Constructor Details

#initialize(url:) ⇒ WebSocketConnection

Returns a new instance of WebSocketConnection.



38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/selenium/webdriver/common/websocket_connection.rb', line 38

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

  @callbacks_mtx = Mutex.new
  @messages_mtx = Mutex.new
  @closing_mtx = Mutex.new

  @closing = false
  @session_id = nil
  @url = url

  process_handshake
  @socket_thread = attach_socket_listener
end

Instance Method Details

#add_callback(event, &block) ⇒ Object



80
81
82
83
84
85
# File 'lib/selenium/webdriver/common/websocket_connection.rb', line 80

def add_callback(event, &block)
  @callbacks_mtx.synchronize do
    callbacks[event] << block
    block.object_id
  end
end

#callbacksObject



76
77
78
# File 'lib/selenium/webdriver/common/websocket_connection.rb', line 76

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

#closeObject



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/selenium/webdriver/common/websocket_connection.rb', line 53

def close
  @closing_mtx.synchronize do
    return if @closing

    @closing = true
  end

  begin
    socket.close
  rescue *CONNECTION_ERRORS => e
    WebDriver.logger.debug "WebSocket listener closed: #{e.class}: #{e.message}", id: :ws
    # already closed
  end

  # Let threads unwind instead of calling exit
  @socket_thread&.join(0.5)
  @callback_threads.list.each do |thread|
    thread.join(0.5)
  rescue StandardError => e
    WebDriver.logger.debug "Failed to join thread during close: #{e.class}: #{e.message}", id: :ws
  end
end

#remove_callback(event, id) ⇒ Object



87
88
89
90
91
92
93
94
95
96
97
# File 'lib/selenium/webdriver/common/websocket_connection.rb', line 87

def remove_callback(event, id)
  @callbacks_mtx.synchronize do
    return if @closing

    callbacks_for_event = callbacks[event]
    return if callbacks_for_event.reject! { |cb| cb.object_id == id }

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

#send_cmd(**payload) ⇒ Object



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/selenium/webdriver/common/websocket_connection.rb', line 99

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

  begin
    socket.write(out_frame.to_s)
  rescue *CONNECTION_ERRORS => e
    raise e, "WebSocket is closed (#{e.class}: #{e.message})"
  end

  wait.until { @messages_mtx.synchronize { messages.delete(id) } }
end