Class: Puppeteer::Bidi::Browser

Inherits:
Object
  • Object
show all
Defined in:
lib/puppeteer/bidi/browser.rb,
sig/puppeteer/bidi/browser.rbs

Overview

Browser represents a browser instance with BiDi connection

Constant Summary collapse

TARGET_EVENTS =

Returns:

  • (Object)
%i[targetcreated targetchanged targetdestroyed].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(connection:, launcher:, core_browser:, session:, ws_endpoint:) ⇒ Browser

Returns a new instance of Browser.

Parameters:



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/puppeteer/bidi/browser.rb', line 54

def initialize(connection:, launcher:, core_browser:, session:, ws_endpoint:)
  @connection = connection
  @launcher = launcher
  @closed = false
  @disconnected = false
  @core_browser = core_browser
  @session = session
  @ws_endpoint = ws_endpoint
  @emitter = Core::EventEmitter.new
  @browser_contexts = {}

  @core_browser.once(:disconnected) { @emitter.dispose }

  # Create default browser context
  default_user_context = @core_browser.default_user_context
  @default_browser_context = browser_context_for(default_user_context)

  register_exit_cleanup if @launcher
end

Instance Attribute Details

#connectionConnection (readonly)

: Connection

Returns:



13
14
15
# File 'lib/puppeteer/bidi/browser.rb', line 13

def connection
  @connection
end

#default_browser_contextBrowserContext (readonly)

: BrowserContext

Returns:



15
16
17
# File 'lib/puppeteer/bidi/browser.rb', line 15

def default_browser_context
  @default_browser_context
end

#processObject (readonly)

: untyped

Returns:

  • (Object)


14
15
16
# File 'lib/puppeteer/bidi/browser.rb', line 14

def process
  @process
end

#ws_endpointString? (readonly)

: String?

Returns:

  • (String, nil)


16
17
18
# File 'lib/puppeteer/bidi/browser.rb', line 16

def ws_endpoint
  @ws_endpoint
end

Class Method Details

.connect(ws_endpoint, timeout: nil, accept_insecure_certs: false) ⇒ Browser

Connect to an existing Firefox browser instance

Parameters:

  • ws_endpoint (String)
  • timeout: (Numeric, nil) (defaults to: nil)
  • accept_insecure_certs: (Boolean) (defaults to: false)

Returns:



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/puppeteer/bidi/browser.rb', line 114

def self.connect(ws_endpoint, timeout: nil, accept_insecure_certs: false)
  transport = Transport.new(ws_endpoint)
  timeout_ms = ((timeout || 30) * 1000).to_i
  AsyncUtils.async_timeout(timeout_ms) { transport.connect }.wait
  connection = Connection.new(transport)

  # Verify that this endpoint speaks WebDriver BiDi (and is ready) before creating a new session.
  status = connection.async_send_command('session.status', {}, timeout: timeout_ms).wait
  unless status.is_a?(Hash) && status['ready'] == true
    raise Error, "WebDriver BiDi endpoint is not ready: #{status.inspect}"
  end

  create(connection: connection, launcher: nil, ws_endpoint: ws_endpoint,
         accept_insecure_certs: accept_insecure_certs)
end

.create(connection:, launcher: nil, ws_endpoint: nil, accept_insecure_certs: false) ⇒ Browser

Parameters:

  • connection: (Connection)
  • launcher: (BrowserLauncher, nil) (defaults to: nil)
  • ws_endpoint: (String, nil) (defaults to: nil)
  • accept_insecure_certs: (Boolean) (defaults to: false)

Returns:



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/puppeteer/bidi/browser.rb', line 23

def self.create(connection:, launcher: nil, ws_endpoint: nil, accept_insecure_certs: false)
  # Create a new BiDi session
  session = Core::Session.from(
    connection: connection,
    capabilities: {
      alwaysMatch: {
        acceptInsecureCerts: accept_insecure_certs,
        unhandledPromptBehavior: { default: 'ignore' },
        webSocketUrl: true,
      },
    },
  ).wait

  core_browser = Core::Browser.from(session).wait
  session.browser = core_browser

  new(
    connection: connection,
    launcher: launcher,
    core_browser: core_browser,
    session: session,
    ws_endpoint: ws_endpoint,
  )
end

.launch(executable_path: nil, user_data_dir: nil, headless: true, args: nil, timeout: nil, accept_insecure_certs: false) ⇒ Browser

Launch a new Firefox browser instance

Parameters:

  • executable_path: (String, nil) (defaults to: nil)
  • user_data_dir: (String, nil) (defaults to: nil)
  • headless: (Boolean) (defaults to: true)
  • args: (Array[String], nil) (defaults to: nil)
  • timeout: (Numeric, nil) (defaults to: nil)
  • accept_insecure_certs: (Boolean) (defaults to: false)

Returns:



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/puppeteer/bidi/browser.rb', line 82

def self.launch(executable_path: nil, user_data_dir: nil, headless: true, args: nil, timeout: nil,
                accept_insecure_certs: false)
  launcher = BrowserLauncher.new(
    executable_path: executable_path,
    user_data_dir: user_data_dir,
    headless: headless,
    args: args || []
  )

  ws_endpoint = launcher.launch

  # Create transport and connection
  transport = Transport.new(ws_endpoint)

  # Start transport connection in background thread with Sync reactor
  # Sync is the preferred way to run async code at the top level
  timeout_ms = ((timeout || 30) * 1000).to_i
  AsyncUtils.async_timeout(timeout_ms) { transport.connect }.wait

  connection = Connection.new(transport)

  browser = create(connection: connection, launcher: launcher, ws_endpoint: ws_endpoint,
                   accept_insecure_certs: accept_insecure_certs)
  _target = browser.wait_for_target { |target| target.type == 'page' }
  browser
end

Instance Method Details

#browser_context_for(user_context) ⇒ BrowserContext

Parameters:

Returns:



501
502
503
504
505
506
507
508
509
510
511
512
513
514
# File 'lib/puppeteer/bidi/browser.rb', line 501

def browser_context_for(user_context)
  return @browser_contexts[user_context.id] if @browser_contexts.key?(user_context.id)

  context = BrowserContext.new(self, user_context)
  TARGET_EVENTS.each do |event|
    context.on(event) do |target|
      @emitter.emit(event, target)
    end
  end
  user_context.once(:closed) do
    @browser_contexts.delete(user_context.id)
  end
  @browser_contexts[user_context.id] = context
end

#closevoid

This method returns an undefined value.

Close the browser



304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/puppeteer/bidi/browser.rb', line 304

def close
  return if @closed

  @closed = true

  begin
    begin
      @connection.async_send_command('browser.close', {}).wait
    rescue StandardError => e
      debug_error(e)
    ensure
      @connection.close
    end
  rescue => e
    debug_error(e)
  ensure
    @emitter.dispose
  end

  @launcher&.kill
end

#closed?Boolean

Returns:

  • (Boolean)


349
350
351
# File 'lib/puppeteer/bidi/browser.rb', line 349

def closed?
  @closed
end

#cookiesArray[Hash[String, untyped]]

Get all cookies in the default browser context.

Returns:

  • (Array[Hash[String, untyped]])


180
181
182
# File 'lib/puppeteer/bidi/browser.rb', line 180

def cookies
  @default_browser_context.cookies
end

#create_browser_contextBrowserContext

Create a new browser context

Returns:



153
154
155
156
# File 'lib/puppeteer/bidi/browser.rb', line 153

def create_browser_context
  user_context = @core_browser.create_user_context.wait
  browser_context_for(user_context)
end

#debug_error(error) ⇒ Object

Parameters:

  • error (Object)

Returns:

  • (Object)


466
467
468
469
470
# File 'lib/puppeteer/bidi/browser.rb', line 466

def debug_error(error)
  return unless ENV['DEBUG_BIDI_COMMAND']

  warn(error.full_message)
end

This method returns an undefined value.

Delete cookies in the default browser context.

Parameters:

  • cookies (Array[Hash[String, untyped]])
  • cookie (Object)


196
197
198
# File 'lib/puppeteer/bidi/browser.rb', line 196

def delete_cookie(*cookies, **cookie)
  @default_browser_context.delete_cookie(*cookies, **cookie)
end

#delete_matching_cookies(*filters, **filter) ⇒ void

This method returns an undefined value.

Delete cookies matching the provided filters in the default browser context.

Parameters:

  • filters (Array[Hash[String, untyped]])
  • filter (Object)


204
205
206
# File 'lib/puppeteer/bidi/browser.rb', line 204

def delete_matching_cookies(*filters, **filter)
  @default_browser_context.delete_matching_cookies(*filters, **filter)
end

#disconnectvoid

This method returns an undefined value.

Disconnect from the browser (does not close the browser process).



328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/puppeteer/bidi/browser.rb', line 328

def disconnect
  return if @closed || @disconnected

  @disconnected = true

  begin
    @session.end_session
  rescue StandardError => e
    debug_error(e)
  ensure
    begin
      @connection.close
    rescue StandardError => e
      debug_error(e)
    ensure
      @emitter.dispose
    end
  end
end

#disconnected?Boolean

Returns:

  • (Boolean)


354
355
356
# File 'lib/puppeteer/bidi/browser.rb', line 354

def disconnected?
  @disconnected
end

#each_targetEnumerator[BrowserTarget | PageTarget | FrameTarget, void] #each_targetvoid

Overloads:

Yields:

Yield Parameters:

Yield Returns:

  • (void)


474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
# File 'lib/puppeteer/bidi/browser.rb', line 474

def each_target(&block)
  return enum_for(:each_target) unless block_given?
  return unless @core_browser

  yield target

  @core_browser.user_contexts.each do |user_context|
    next if user_context.disposed?

    browser_context = browser_context_for(user_context)
    next unless browser_context

    browser_context.targets.each { |target| yield target }
  end
end

#find_target(predicate) ⇒ BrowserTarget, ...



492
493
494
495
496
497
# File 'lib/puppeteer/bidi/browser.rb', line 492

def find_target(predicate)
  each_target do |target|
    return target if predicate.call(target)
  end
  nil
end

#get_window_bounds(window_id) ⇒ Hash[Symbol, untyped]

Get browser window bounds for a given window ID.

Parameters:

  • window_id (String)

Returns:

  • (Hash[Symbol, untyped])


219
220
221
222
223
224
225
226
227
228
# File 'lib/puppeteer/bidi/browser.rb', line 219

def get_window_bounds(window_id)
  info = @core_browser.get_client_window_info(window_id).wait
  {
    left: info['x'],
    top: info['y'],
    width: info['width'],
    height: info['height'],
    window_state: info['state']
  }
end

#new_page(background: nil, type: nil, window_bounds: nil) ⇒ Page

Create a new page (Puppeteer-like API)

Parameters:

  • background: (Boolean, nil) (defaults to: nil)
  • type: (String, nil) (defaults to: nil)
  • window_bounds: (Hash[Symbol | String, untyped], nil) (defaults to: nil)

Returns:



147
148
149
# File 'lib/puppeteer/bidi/browser.rb', line 147

def new_page(background: nil, type: nil, window_bounds: nil)
  @default_browser_context.new_page(background: background, type: type, window_bounds: window_bounds)
end

#off(event, &block) ⇒ void

This method returns an undefined value.

Remove an event handler

Parameters:

  • event (String, Symbol)


293
294
295
296
297
298
299
300
# File 'lib/puppeteer/bidi/browser.rb', line 293

def off(event, &block)
  if TARGET_EVENTS.include?(event.to_sym)
    @emitter.off(event, &block)
  else
    @connection.off(event, &block)
  end
  self
end

#on(event) {|arg0| ... } ⇒ Browser

Register event handler

Parameters:

  • event (String, Symbol)

Yields:

Yield Parameters:

  • arg0 (Object)

Yield Returns:

  • (void)

Returns:



262
263
264
265
266
267
268
269
# File 'lib/puppeteer/bidi/browser.rb', line 262

def on(event, &block)
  if TARGET_EVENTS.include?(event.to_sym)
    @emitter.on(event, &block)
  else
    @connection.on(event, &block)
  end
  self
end

#once(event) {|arg0| ... } ⇒ Browser

Register a one-time event handler

Parameters:

  • event (String, Symbol)

Yields:

Yield Parameters:

  • arg0 (Object)

Yield Returns:

  • (void)

Returns:



275
276
277
278
279
280
281
282
283
284
285
286
287
# File 'lib/puppeteer/bidi/browser.rb', line 275

def once(event, &block)
  if TARGET_EVENTS.include?(event.to_sym)
    @emitter.once(event, &block)
  else
    # @type var wrapper: ^(untyped) -> void
    wrapper = proc do |data|
      @connection.off(event, &wrapper)
      block.call(data)
    end
    @connection.on(event, &wrapper)
  end
  self
end

#pagesArray[Page]

Get all pages

Returns:



160
161
162
163
164
# File 'lib/puppeteer/bidi/browser.rb', line 160

def pages
  return [] if @closed || @disconnected

  @default_browser_context.pages
end

#register_exit_cleanupvoid

This method returns an undefined value.



454
455
456
457
458
459
460
461
462
463
464
# File 'lib/puppeteer/bidi/browser.rb', line 454

def register_exit_cleanup
  at_exit do
    next if @closed || @disconnected

    begin
      @launcher&.kill
    rescue StandardError => e
      debug_error(e)
    end
  end
end

This method returns an undefined value.

Set cookies in the default browser context.

Parameters:

  • cookies (Array[Hash[String, untyped]])
  • cookie (Object)


188
189
190
# File 'lib/puppeteer/bidi/browser.rb', line 188

def set_cookie(*cookies, **cookie)
  @default_browser_context.set_cookie(*cookies, **cookie)
end

#set_permission(origin, *permissions) ⇒ void

This method returns an undefined value.

Set permission states in the default browser context.

Parameters:

  • origin (String, Symbol)
  • permissions (Hash[Symbol | String, untyped])


212
213
214
# File 'lib/puppeteer/bidi/browser.rb', line 212

def set_permission(origin, *permissions)
  @default_browser_context.set_permission(origin, *permissions)
end

#set_window_bounds(window_id, window_bounds) ⇒ void

This method returns an undefined value.

Set browser window bounds for a given window ID.

Parameters:

  • window_id (String)
  • window_bounds (Hash[Symbol | String, untyped])


234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/puppeteer/bidi/browser.rb', line 234

def set_window_bounds(window_id, window_bounds)
  normalized = window_bounds.transform_keys(&:to_sym)
  window_state = normalized[:window_state] || normalized[:windowState] || 'normal'
  window_state = window_state.to_s

  params = if window_state == 'normal'
             {
               clientWindow: window_id,
               state: 'normal',
               x: normalized[:left],
               y: normalized[:top],
               width: normalized[:width],
               height: normalized[:height]
             }
           else
             {
               clientWindow: window_id,
               state: window_state
             }
           end

  @core_browser.set_client_window_state(params).wait
end

#statusObject

Get BiDi session status

Returns:

  • (Object)


132
133
134
# File 'lib/puppeteer/bidi/browser.rb', line 132

def status
  @connection.async_send_command('session.status').wait
end

#targetBrowserTarget

Get the browser target.

Returns:



174
175
176
# File 'lib/puppeteer/bidi/browser.rb', line 174

def target
  @target ||= BrowserTarget.new(self)
end

#targetsArray[BrowserTarget | PageTarget | FrameTarget]

Get all known targets.

Returns:



168
169
170
# File 'lib/puppeteer/bidi/browser.rb', line 168

def targets
  each_target.to_a
end

#user_agentString

Get the browser's original user agent

Returns:

  • (String)


138
139
140
# File 'lib/puppeteer/bidi/browser.rb', line 138

def user_agent
  @session.capabilities["userAgent"]
end

#wait_for_exitvoid

This method returns an undefined value.

Wait for browser process to exit



447
448
449
# File 'lib/puppeteer/bidi/browser.rb', line 447

def wait_for_exit
  @launcher&.wait
end

#wait_for_target(timeout: nil) {|arg0| ... } ⇒ BrowserTarget, ...

Wait until a target (top-level browsing context) satisfies the predicate.

Parameters:

  • timeout: (Integer, nil) (defaults to: nil)

Yields:

Yield Parameters:

Yield Returns:

  • (boolish)

Returns:



362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
# File 'lib/puppeteer/bidi/browser.rb', line 362

def wait_for_target(timeout: nil, &predicate)
  predicate ||= ->(_target) { true }
  timeout_ms = timeout || 30_000
  raise ArgumentError, 'timeout must be >= 0' if timeout_ms && timeout_ms.negative?

  if (target = find_target(predicate))
    return target
  end

  promise = Async::Promise.new
  session_listeners = []
  browser_listeners = []

  cleanup = lambda do
    session_listeners.each do |event, listener|
      @session.off(event, &listener)
    end
    session_listeners.clear

    browser_listeners.each do |event, listener|
      @core_browser.off(event, &listener)
    end
    browser_listeners.clear
  end

  check_and_resolve = lambda do
    return if promise.resolved?

    begin
      if (match = find_target(predicate))
        promise.resolve(match)
        cleanup.call
      end
    rescue => error
      promise.reject(error) unless promise.resolved?
      cleanup.call
    end
  end

  session_listener = proc { |_data| check_and_resolve.call }
  session_events = [
    :'browsingContext.contextCreated',
    :'browsingContext.navigationStarted',
    :'browsingContext.navigationCommitted',
    :'browsingContext.historyUpdated',
    :'browsingContext.fragmentNavigated',
    :'browsingContext.domContentLoaded',
    :'browsingContext.load'
  ]

  session_events.each do |event|
    @session.on(event, &session_listener)
    session_listeners << [event, session_listener]
  end

  browser_disconnect_listener = proc do |reason|
    next if promise.resolved?

    promise.reject(Core::BrowserDisconnectedError.new(reason || 'Browser disconnected'))
    cleanup.call
  end

  @core_browser.on(:disconnected, &browser_disconnect_listener)
  browser_listeners << [:disconnected, browser_disconnect_listener]

  # Re-check after listeners are set up to avoid missing fast events.
  check_and_resolve.call

  begin
    result = if timeout_ms
               AsyncUtils.async_timeout(timeout_ms, promise).wait
             else
               promise.wait
             end
  rescue Async::TimeoutError
    raise TimeoutError, "Waiting for target failed: timeout #{timeout_ms}ms exceeded"
  ensure
    cleanup.call
  end

  result
end