Class: Selenium::WebDriver::WebSocketConnection Private

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

This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.

Constant Summary collapse

CONNECTION_ERRORS =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

[
  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 =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

30
RESPONSE_WAIT_INTERVAL =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

0.1
MAX_LOG_MESSAGE_SIZE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

9999
MAX_FRAME_SIZE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

websocket-ruby defaults to a 20MB limit and silently drops larger frames, which can stall the listener. CDP payloads (e.g. large data: URLs) can exceed that, so raise the ceiling for our connections. The gem only exposes the limit as process-global state, so it is raised (never lowered) and not restored on close - concurrent connections share the value. Other bindings rely on their own websocket clients' limits; this constant only affects websocket-ruby.

100 * 1024 * 1024

Instance Method Summary collapse

Constructor Details

#initialize(url:) ⇒ WebSocketConnection

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

100MB



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/selenium/webdriver/common/websocket_connection.rb', line 48

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

  apply_frame_size_limit
  process_handshake
  @socket_thread = attach_socket_listener
end

Instance Method Details

#add_callback(event, &block) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



83
84
85
86
87
88
# File 'lib/selenium/webdriver/common/websocket_connection.rb', line 83

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

#callbacksObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



79
80
81
# File 'lib/selenium/webdriver/common/websocket_connection.rb', line 79

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

#closeObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Idempotent: the listener may already have initiated shutdown (see #frame_dropped?), so always close the socket and join threads rather than short-circuiting on @closing.



67
68
69
70
71
72
73
74
75
76
77
# File 'lib/selenium/webdriver/common/websocket_connection.rb', line 67

def close
  close_socket

  # Let threads unwind instead of calling exit
  @socket_thread&.join(0.5) unless @socket_thread == Thread.current
  @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

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



90
91
92
93
94
95
96
97
98
99
100
# File 'lib/selenium/webdriver/common/websocket_connection.rb', line 90

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

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Raises:

  • (IOError)


102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/selenium/webdriver/common/websocket_connection.rb', line 102

def send_cmd(**payload)
  # IOError to match what writing to an already-closed socket raises
  raise IOError, 'WebSocket connection is closed' if @closing

  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 do
    raise IOError, 'WebSocket connection closed while waiting for a response' if @closing

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