Class: Puppeteer::Connection

Inherits:
Object
  • Object
show all
Includes:
DebugPrint, EventCallbackable
Defined in:
lib/puppeteer/connection.rb,
sig/_supplementary.rbs

Defined Under Namespace

Classes: MessageCallback, ProtocolError, RequestDebugPrinter, ResponseDebugPrinter

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from EventCallbackable

#add_event_listener, #emit_event, #observe_first, #off, #on_event, #remove_event_listener

Methods included from DebugPrint

#debug_print, #debug_puts

Constructor Details

#initialize(url, transport, delay = 0, protocol_timeout: nil) ⇒ Connection

Returns a new instance of Connection.

Parameters:



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/puppeteer/connection.rb', line 40

def initialize(url, transport, delay = 0, protocol_timeout: nil)
  @url = url
  @last_id = 0
  @callbacks = {}
  @callbacks_mutex = Mutex.new
  @delay = delay
  @protocol_timeout = protocol_timeout
  @reject_emulate_network_conditions_calls = false

  @network_message_queue = Async::Queue.new
  @network_message_task = nil

  @transport = transport
  @transport.on_message do |data|
    message = JSON.parse(data)
    sleep_before_handling_message(message)
    if network_event_message?(message)
      enqueue_network_message(message)
    elsif should_handle_synchronously?(message)
      handle_message(message)
    else
      async_handle_message(message)
    end
  end
  @transport.on_close do |reason, code|
    handle_close
  end

  @sessions = {}
  @sessions_mutex = Mutex.new
  @closed = false
  @manually_attached = Set.new
end

Instance Attribute Details

#protocol_timeoutObject (readonly)

Returns the value of attribute protocol_timeout.



74
75
76
# File 'lib/puppeteer/connection.rb', line 74

def protocol_timeout
  @protocol_timeout
end

#reject_emulate_network_conditions_calls=(value) ⇒ Object (writeonly)

Sets the attribute reject_emulate_network_conditions_calls

Parameters:

  • value

    the value to set the attribute reject_emulate_network_conditions_calls to.



75
76
77
# File 'lib/puppeteer/connection.rb', line 75

def reject_emulate_network_conditions_calls=(value)
  @reject_emulate_network_conditions_calls = value
end

Class Method Details

.from_session(session) ⇒ Puppeteer::Connection?

Parameters:

Returns:



161
162
163
# File 'lib/puppeteer/connection.rb', line 161

def self.from_session(session)
  session.connection
end

Instance Method Details

#async_send_message(method, params = {}) ⇒ Object

Parameters:

  • method (String)
  • params (Hash[Symbol | String, untyped]) (defaults to: {})

Returns:

  • (Object)


189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/puppeteer/connection.rb', line 189

def async_send_message(method, params = {})
  ensure_command_allowed!(method)
  promise = Async::Promise.new

  generate_id do |id|
    @callbacks_mutex.synchronize do
      @callbacks[id] = MessageCallback.new(method: method, promise: promise)
    end
    raw_send(id: id, message: { method: method, params: params })
  end

  promise
end

#auto_attached?(target_id) ⇒ Boolean

Parameters:

  • target_id (String)

Returns:

  • (Boolean)


399
400
401
# File 'lib/puppeteer/connection.rb', line 399

def auto_attached?(target_id)
  !@manually_attached.include?(target_id)
end

#closed?Boolean

used only in Browser#connected?

Returns:

  • (Boolean)


87
88
89
# File 'lib/puppeteer/connection.rb', line 87

def closed?
  @closed
end

#create_session(target_info, auto_attach_emulated: false) ⇒ CDPSession

Parameters:

  • targetInfo (Protocol.Target.TargetInfo)
  • target_info (Object)
  • auto_attach_emulated: (Boolean) (defaults to: false)

Returns:



405
406
407
408
409
410
411
412
413
# File 'lib/puppeteer/connection.rb', line 405

def create_session(target_info, auto_attach_emulated: false)
  unless auto_attach_emulated
    @manually_attached << target_info.target_id
  end
  result = send_message('Target.attachToTarget', targetId: target_info.target_id, flatten: true)
  session_id = result['sessionId']
  @manually_attached.delete(target_info.target_id)
  @sessions_mutex.synchronize { @sessions[session_id] }.tap { |session| session&.mark_ready }
end

#disposevoid

This method returns an undefined value.



394
395
396
397
# File 'lib/puppeteer/connection.rb', line 394

def dispose
  handle_close
  @transport.close
end

#enqueue_network_message(message) ⇒ void

This method returns an undefined value.

Parameters:

  • message (Hash[String, untyped])


148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/puppeteer/connection.rb', line 148

private def enqueue_network_message(message)
  if Async::Task.current?
    ensure_network_message_task
    begin
      @network_message_queue.enqueue(message)
    rescue Async::Queue::ClosedError
      # Connection is closing; ignore late network events.
    end
  else
    handle_message(message)
  end
end

#ensure_command_allowed!(method) ⇒ void

This method returns an undefined value.

Parameters:

  • method (String)

Raises:



77
78
79
80
81
82
83
84
# File 'lib/puppeteer/connection.rb', line 77

def ensure_command_allowed!(method)
  return unless method == 'Network.emulateNetworkConditions'
  return unless @reject_emulate_network_conditions_calls

  raise Puppeteer::Error.new(
    'Cannot reset network conditions: rule-based emulation is enabled.',
  )
end

#ensure_network_message_taskvoid

This method returns an undefined value.



137
138
139
140
141
142
143
144
145
146
# File 'lib/puppeteer/connection.rb', line 137

private def ensure_network_message_task
  return if @network_message_task&.alive?
  return unless Async::Task.current?

  @network_message_task = Async do
    while (queued = @network_message_queue.dequeue)
      handle_message(queued)
    end
  end
end

#generate_id {|arg0| ... } ⇒ Object

package private. not intended to use externally.

connection.generate_id do |generated_id|
  # play with generated_id
end

Yields:

Yield Parameters:

  • arg0 (Integer)

Yield Returns:

  • (Object)

Returns:

  • (Object)


211
212
213
# File 'lib/puppeteer/connection.rb', line 211

def generate_id(&block)
  block.call(@last_id += 1)
end

#handle_closevoid

This method returns an undefined value.



359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
# File 'lib/puppeteer/connection.rb', line 359

private def handle_close
  return if @closed
  @closed = true
  @transport.on_message
  @transport.on_close
  @network_message_queue.close
  begin
    @network_message_task&.stop
  rescue Async::Stop
    # Task already stopping; ignore.
  end
  callbacks = @callbacks_mutex.synchronize do
    @callbacks.values.tap { @callbacks.clear }
  end
  callbacks.each do |callback|
    callback.reject(
      ProtocolError.new(
        method: callback.method,
        error_message: 'Target Closed.'))
  end
  sessions = @sessions_mutex.synchronize do
    @sessions.values.tap { @sessions.clear }
  end
  sessions.each(&:handle_closed)
  emit_event(ConnectionEmittedEvents::Disconnected)
end

#handle_message(message) ⇒ void

This method returns an undefined value.

Parameters:

  • message (Hash[String, untyped])


295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
# File 'lib/puppeteer/connection.rb', line 295

private def handle_message(message)
  if @delay > 0
    Puppeteer::AsyncUtils.sleep_seconds(@delay / 1000.0)
  end

  response_debug_printer.handle_message(message)

  case message['method']
  when 'Target.attachedToTarget'
    session_id = message['params']['sessionId']
    parent_session =
      if message['sessionId']
        @sessions_mutex.synchronize { @sessions[message['sessionId']] }
      end
    session = Puppeteer::CDPSession.new(
      self,
      message['params']['targetInfo']['type'],
      session_id,
      parent_session: parent_session,
    )
    @sessions_mutex.synchronize { @sessions[session_id] = session }
    emit_event('sessionattached', session)
    parent_session&.emit_event('sessionattached', session)
  when 'Target.detachedFromTarget'
    session_id = message['params']['sessionId']
    session = @sessions_mutex.synchronize { @sessions[session_id] }
    if session
      session.handle_closed
      @sessions_mutex.synchronize { @sessions.delete(session_id) }
      emit_event('sessiondetached', session)
      if message['sessionId']
        parent_session = @sessions_mutex.synchronize { @sessions[message['sessionId']] }
        parent_session&.emit_event('sessiondetached', session)
      end
    end
  end

  if message['sessionId']
    session_id = message['sessionId']
    @sessions_mutex.synchronize { @sessions[session_id] }&.handle_message(message)
  elsif message['id']
    # Callbacks could be all rejected if someone has called `.dispose()`.
    if callback = @callbacks_mutex.synchronize { @callbacks.delete(message['id']) }
      if message['error']
        callback.reject(
          ProtocolError.new(
            method: callback.method,
            error_message: message['error']['message'],
            error_data: message['error']['data']))
      else
        callback.resolve(message['result'])
      end
    else
      sessions = @sessions_mutex.synchronize { @sessions.values }
      session = sessions.find { |candidate| candidate.callback?(message['id']) }
      session&.handle_message(message)
    end
  else
    emit_event(message['method'], message['params'])
  end
end

#network_event_message?(message) ⇒ Boolean

Parameters:

  • message (Hash[String, untyped])

Returns:

  • (Boolean)


133
134
135
# File 'lib/puppeteer/connection.rb', line 133

private def network_event_message?(message)
  message['method']&.start_with?('Network.')
end

#on_close {|arg0, arg1| ... } ⇒ void

This method returns an undefined value.

Yields:

Yield Parameters:

  • arg0 (Object)
  • arg1 (Object)

Yield Returns:

  • (Object)


386
387
388
# File 'lib/puppeteer/connection.rb', line 386

def on_close(&block)
  @on_close = block
end

#on_message {|arg0| ... } ⇒ void

This method returns an undefined value.

Yields:

Yield Parameters:

  • arg0 (String)

Yield Returns:

  • (Object)


390
391
392
# File 'lib/puppeteer/connection.rb', line 390

def on_message(&block)
  @on_message = block
end

#raw_send(id:, message:) ⇒ void

This method returns an undefined value.

package private. not intended to use externally.

Parameters:

  • id: (Integer)
  • message: (Hash[Symbol | String, untyped])


216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/puppeteer/connection.rb', line 216

def raw_send(id:, message:)
  # In original puppeteer (JS) implementation,
  # id is generated here using #generate_id and the id argument is not passed to #raw_send.
  #
  # However with concurrent-ruby, '#handle_message' is sometimes called
  # just soon after @transport.send_text and **before returning the id.**
  #
  # So we have to know the message id in advance before send_text.
  #
  payload = JSON.generate(message.compact.merge(id: id))
  @transport.send_text(payload)
  request_debug_printer.handle_payload(payload)
end

#request_debug_printerObject

Returns:

  • (Object)


287
288
289
# File 'lib/puppeteer/connection.rb', line 287

private def request_debug_printer
  @request_debug_printer ||= RequestDebugPrinter.new
end

#response_debug_printerObject

Returns:

  • (Object)


291
292
293
# File 'lib/puppeteer/connection.rb', line 291

private def response_debug_printer
  @response_debug_printer ||= ResponseDebugPrinter.new
end

#send_message(method, params = {}) ⇒ Object

Parameters:

  • method (string)
  • params (!Object=) (defaults to: {})

Returns:

  • (Object)


177
178
179
180
181
182
183
184
185
186
187
# File 'lib/puppeteer/connection.rb', line 177

def send_message(method, params = {})
  if @protocol_timeout && @protocol_timeout > 0
    begin
      Puppeteer::AsyncUtils.async_timeout(@protocol_timeout, async_send_message(method, params)).wait
    rescue Async::TimeoutError
      raise ProtocolError.new(method: method, error_message: "Timeout #{@protocol_timeout}ms exceeded")
    end
  else
    async_send_message(method, params).wait
  end
end

#session(session_id) ⇒ ?CDPSession

Parameters:

  • sessionId (string)
  • session_id (String)

Returns:



167
168
169
# File 'lib/puppeteer/connection.rb', line 167

def session(session_id)
  @sessions_mutex.synchronize { @sessions[session_id] }
end

#should_handle_synchronously?(message) ⇒ Boolean

Parameters:

  • message (Hash[String, untyped])

Returns:

  • (Boolean)


100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/puppeteer/connection.rb', line 100

private def should_handle_synchronously?(message)
  return true if message['id']

  case message['method']
  when nil
    false
  when /^Network\./
    # Network events are queued for ordered async processing.
    false
  when /^Page\.frame/
    # Page.frameAttached
    # Page.frameNavigated
    # Page.frameDetached
    # Page.frameStoppedLoading
    true
  when 'Page.lifecycleEvent'
    true
  when /^Runtime\.executionContext/
    # - Runtime.executionContextCreated
    # - Runtime.executionContextDestroyed
    # - Runtime.executionContextsCleared
    # These events should be strictly ordered.
    true
  when 'Target.attachedToTarget', 'Target.detachedFromTarget'
    true
  when 'Target.targetCreated'
    # type=page must be handled asynchronously for avoiding wait timeout...
    message.dig('params', 'targetInfo', 'type') == 'browser'
  else
    false
  end
end

#sleep_before_handling_message(message) ⇒ void

This method returns an undefined value.

Parameters:

  • message (Hash[String, untyped])


91
92
93
94
95
96
97
98
# File 'lib/puppeteer/connection.rb', line 91

private def sleep_before_handling_message(message)
  # Keep network events ordered without extra delay.
  return if message['method']&.start_with?('Network.')

  # For some reasons, sleeping a bit reduces trivial errors...
  # 4ms is an interval of internal shared timer of WebKit.
  Puppeteer::AsyncUtils.sleep_seconds(0.004)
end

#urlString

Returns:

  • (String)


171
172
173
# File 'lib/puppeteer/connection.rb', line 171

def url
  @url
end