Class: Ferrum::Context

Inherits:
Object
  • Object
show all
Defined in:
lib/ferrum/context.rb

Overview

Represents a browser context, i.e. an isolated browsing profile (similar to an incognito window) with its own cookies, cache and storage. Keeps track of the Targets that belong to it and connects to them as Pages or Workers. Managed by Contexts, which owns the browser's collection of contexts and routes CDP target events to the right one.

Constant Summary collapse

POSITION =
%i[first last].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(client, contexts, id) ⇒ Context

Returns a new instance of Context.



18
19
20
21
22
23
24
# File 'lib/ferrum/context.rb', line 18

def initialize(client, contexts, id)
  @id = id
  @client = client
  @contexts = contexts
  @targets = Concurrent::Map.new
  @pendings = Concurrent::Map.new
end

Instance Attribute Details

#idObject (readonly)

Returns the value of attribute id.



16
17
18
# File 'lib/ferrum/context.rb', line 16

def id
  @id
end

#targetsObject (readonly)

Returns the value of attribute targets.



16
17
18
# File 'lib/ferrum/context.rb', line 16

def targets
  @targets
end

Instance Method Details

#add_target(params:, session_id: nil) ⇒ Target

Registers a target discovered via a CDP Target.* event, or updates the session id on one already known. Called by Ferrum::Contexts as targets are created/attached.

Parameters:

  • params (Hash)

    The target's targetInfo.

  • session_id (String, nil) (defaults to: nil)

Returns:



116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/ferrum/context.rb', line 116

def add_target(params:, session_id: nil)
  new_target = Target.new(@client, session_id, params)
  # `put_if_absent` returns nil if added a new value or existing if there was one already
  target = @targets.put_if_absent(new_target.id, new_target) || new_target
  # on first iteration session_id may be nil, then if session is present here we must set it to the target
  target.session_id = session_id if session_id && target.session_id.nil?
  @default_target ||= target

  new_pending = Concurrent::IVar.new
  pending = @pendings.put_if_absent(target.id, new_pending) || new_pending
  pending.try_set(true)
  target
end

#attach_target(target_id) ⇒ Object

Manually attaches to a target, e.g. a service worker discovered via #service_workers. Once attached, target.worker/target.page returns a connected Worker/Page for it.

Note: attaching to a service worker's session prevents Chrome from ever terminating it while the connection is open.

Raises:



156
157
158
159
160
161
162
163
164
# File 'lib/ferrum/context.rb', line 156

def attach_target(target_id)
  target = @targets[target_id]
  raise NoSuchTargetError unless target

  @contexts.manually_attached(target_id)
  session = @client.command("Target.attachToTarget", targetId: target_id, flatten: true)
  target.session_id = session["sessionId"]
  true
end

#close_targets_connectionvoid

This method returns an undefined value.

Closes the WebSocket connection of every connected target, without disposing the targets themselves.



179
180
181
182
183
184
185
# File 'lib/ferrum/context.rb', line 179

def close_targets_connection
  @targets.each_value do |target|
    next unless target.connected?

    target.close_connection
  end
end

#create_page(**options) ⇒ Page

Creates a new target in this context and eagerly connects to it as a Page, forwarding options to Target#build_page.

Parameters:

  • options (Hash)

Returns:



83
84
85
86
# File 'lib/ferrum/context.rb', line 83

def create_page(**options)
  target = create_target
  target.page = target.build_page(**options)
end

#create_targetTarget

Creates a new target in this context via Target.createTarget and blocks until it's been registered (see #add_target).

Returns:

Raises:



94
95
96
97
98
99
100
101
102
103
104
# File 'lib/ferrum/context.rb', line 94

def create_target
  target_id = @client.command("Target.createTarget", browserContextId: @id, url: "about:blank")["targetId"]

  new_pending = Concurrent::IVar.new
  pending = @pendings.put_if_absent(target_id, new_pending) || new_pending
  resolved = pending.value(@client.protocol_timeout)
  raise NoSuchTargetError unless resolved

  @pendings.delete(target_id)
  @targets[target_id]
end

#default_targetTarget

The context's first known target, creating one via Target.createTarget if none has attached yet.

Returns:



30
31
32
# File 'lib/ferrum/context.rb', line 30

def default_target
  @default_target ||= create_target
end

#delete_target(target_id) ⇒ Target?

Removes a target, e.g. on Target.targetDestroyed/targetCrashed.

Parameters:

  • target_id (String)

Returns:



146
147
148
# File 'lib/ferrum/context.rb', line 146

def delete_target(target_id)
  @targets.delete(target_id)
end

#disposeBoolean

Disposes this browser context and all of its targets.

Returns:

  • (Boolean)


190
191
192
# File 'lib/ferrum/context.rb', line 190

def dispose
  @contexts.dispose(@id)
end

#find_targetTarget?

Returns the first target for which the block returns truthy.

Returns:



169
170
171
172
173
# File 'lib/ferrum/context.rb', line 169

def find_target
  @targets.each_value { |t| return t if yield(t) }

  nil
end

#inspectString

Debug representation of the context, including its known targets.

Returns:

  • (String)


206
207
208
# File 'lib/ferrum/context.rb', line 206

def inspect
  %(#<#{self.class} @id=#{@id.inspect} @targets=#{@targets.inspect} @default_target=#{@default_target.inspect}>)
end

#pagePage

Connects to and returns the #default_target's page.

Returns:



37
38
39
# File 'lib/ferrum/context.rb', line 37

def page
  default_target.page
end

#pagesArray<Page>

All page targets in this context, connected to as Pages.

Returns:



44
45
46
# File 'lib/ferrum/context.rb', line 44

def pages
  @targets.values.select(&:page?).map(&:page)
end

#service_workersArray<Target>

Service worker targets registered in this context. Unlike #workers, these are plain Targets and are not connected to. Attaching to a service worker's session keeps it alive indefinitely, so we only do that on demand, via target.worker.

Returns:



61
62
63
# File 'lib/ferrum/context.rb', line 61

def service_workers
  @targets.values.select(&:service_worker?)
end

#target?(target_id) ⇒ Boolean

Whether a target with the given id is known in this context.

Parameters:

  • target_id (String)

Returns:

  • (Boolean)


199
200
201
# File 'lib/ferrum/context.rb', line 199

def target?(target_id)
  !!@targets[target_id]
end

#update_target(target_id, params) ⇒ void

This method returns an undefined value.

Updates a known target's params, e.g. on Target.targetInfoChanged.

Parameters:

  • target_id (String)
  • params (Hash)


137
138
139
# File 'lib/ferrum/context.rb', line 137

def update_target(target_id, params)
  @targets[target_id]&.update(params)
end

#windows(pos = nil, size = 1) ⇒ Object

When we call page method on target it triggers ruby to connect to given page by WebSocket, if there are many opened windows, but we need only one it makes more sense to get and connect to the needed one only which usually is the last one.

Raises:

  • (ArgumentError)


69
70
71
72
73
74
75
# File 'lib/ferrum/context.rb', line 69

def windows(pos = nil, size = 1)
  raise ArgumentError if pos && !POSITION.include?(pos)

  windows = @targets.values.select(&:window?)
  windows = windows.send(pos, size) if pos
  windows.map(&:page)
end

#workersArray<Worker>

Dedicated and shared workers spawned by any page in this context.

Returns:



51
52
53
# File 'lib/ferrum/context.rb', line 51

def workers
  @targets.values.select { |t| t.worker? || t.shared_worker? }.map(&:worker)
end