ferrum 0.17.2 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/ferrum/accessibility/ax_node.rb +108 -0
- data/lib/ferrum/accessibility.rb +106 -0
- data/lib/ferrum/browser/binary.rb +41 -0
- data/lib/ferrum/browser/command.rb +20 -0
- data/lib/ferrum/browser/options/base.rb +67 -0
- data/lib/ferrum/browser/options/chrome.rb +36 -1
- data/lib/ferrum/browser/options/firefox.rb +32 -0
- data/lib/ferrum/browser/options.rb +41 -2
- data/lib/ferrum/browser/process.rb +51 -0
- data/lib/ferrum/browser/xvfb.rb +24 -0
- data/lib/ferrum/browser.rb +32 -10
- data/lib/ferrum/client/subscriber.rb +58 -0
- data/lib/ferrum/client/web_socket.rb +62 -11
- data/lib/ferrum/client.rb +199 -8
- data/lib/ferrum/context.rb +97 -4
- data/lib/ferrum/contexts.rb +119 -9
- data/lib/ferrum/cookies/cookie.rb +6 -0
- data/lib/ferrum/cookies.rb +5 -0
- data/lib/ferrum/dialog.rb +18 -2
- data/lib/ferrum/downloads.rb +52 -0
- data/lib/ferrum/errors.rb +58 -3
- data/lib/ferrum/frame/dom.rb +17 -0
- data/lib/ferrum/frame/runtime.rb +48 -7
- data/lib/ferrum/frame.rb +58 -1
- data/lib/ferrum/headers.rb +6 -0
- data/lib/ferrum/interceptable.rb +62 -0
- data/lib/ferrum/keyboard.rb +25 -0
- data/lib/ferrum/mouse.rb +6 -0
- data/lib/ferrum/network/auth_request.rb +81 -2
- data/lib/ferrum/network/error.rb +15 -0
- data/lib/ferrum/network/exchange.rb +10 -0
- data/lib/ferrum/network/intercepted_request.rb +94 -2
- data/lib/ferrum/network/request.rb +1 -1
- data/lib/ferrum/network/response.rb +2 -0
- data/lib/ferrum/network.rb +121 -9
- data/lib/ferrum/node.rb +305 -15
- data/lib/ferrum/page/animation.rb +5 -0
- data/lib/ferrum/page/frames.rb +69 -7
- data/lib/ferrum/page/screencast.rb +5 -0
- data/lib/ferrum/page/screenshot.rb +52 -20
- data/lib/ferrum/page/stream.rb +56 -0
- data/lib/ferrum/page/tracing.rb +6 -0
- data/lib/ferrum/page.rb +139 -42
- data/lib/ferrum/proxy.rb +52 -2
- data/lib/ferrum/rgba.rb +10 -0
- data/lib/ferrum/target.rb +124 -1
- data/lib/ferrum/utils/attempt.rb +20 -0
- data/lib/ferrum/utils/elapsed_time.rb +38 -0
- data/lib/ferrum/utils/event.rb +14 -0
- data/lib/ferrum/utils/platform.rb +21 -0
- data/lib/ferrum/utils/thread.rb +12 -0
- data/lib/ferrum/version.rb +1 -1
- data/lib/ferrum/worker.rb +125 -0
- data/lib/ferrum.rb +7 -0
- metadata +6 -16
data/lib/ferrum/client.rb
CHANGED
|
@@ -7,9 +7,29 @@ require "ferrum/client/web_socket"
|
|
|
7
7
|
require "ferrum/utils/thread"
|
|
8
8
|
|
|
9
9
|
module Ferrum
|
|
10
|
+
#
|
|
11
|
+
# A thin wrapper around {Client} that scopes commands and events to a
|
|
12
|
+
# single CDP session (a `sessionId` obtained via `Target.attachToTarget`).
|
|
13
|
+
# Targets connect through one of these rather than the top-level {Client}
|
|
14
|
+
# directly, so their commands/events don't leak into other sessions.
|
|
15
|
+
# Method calls not defined here are forwarded to the underlying {Client}.
|
|
16
|
+
#
|
|
10
17
|
class SessionClient
|
|
11
18
|
attr_reader :client, :session_id
|
|
12
19
|
|
|
20
|
+
#
|
|
21
|
+
# Builds the internal event key used to route an event to a specific
|
|
22
|
+
# session, joining the CDP event name with a session id (or leaving it
|
|
23
|
+
# session-less when `session_id` is `nil`).
|
|
24
|
+
#
|
|
25
|
+
# @param [String] event
|
|
26
|
+
# The CDP event name, e.g. `"Page.loadEventFired"`.
|
|
27
|
+
#
|
|
28
|
+
# @param [String, nil] session_id
|
|
29
|
+
# The target session id, or `nil` for the browser-wide session.
|
|
30
|
+
#
|
|
31
|
+
# @return [String]
|
|
32
|
+
#
|
|
13
33
|
def self.event_name(event, session_id)
|
|
14
34
|
[event, session_id].compact.join("_")
|
|
15
35
|
end
|
|
@@ -19,31 +39,93 @@ module Ferrum
|
|
|
19
39
|
@session_id = session_id
|
|
20
40
|
end
|
|
21
41
|
|
|
22
|
-
|
|
42
|
+
#
|
|
43
|
+
# Sends a CDP command scoped to this session.
|
|
44
|
+
#
|
|
45
|
+
# @param [String] method
|
|
46
|
+
# The CDP method name, e.g. `"Page.navigate"`.
|
|
47
|
+
#
|
|
48
|
+
# @param [Boolean] async
|
|
49
|
+
# Whether to send the command without waiting for a response.
|
|
50
|
+
#
|
|
51
|
+
# @param [Hash] params
|
|
52
|
+
# The command's parameters.
|
|
53
|
+
#
|
|
54
|
+
# @param [Numeric, nil] timeout
|
|
55
|
+
# How long to wait for this command's response, overriding
|
|
56
|
+
# {Browser::Options#protocol_timeout}. See {Client#send_message}.
|
|
57
|
+
#
|
|
58
|
+
# @return [Boolean, Hash]
|
|
59
|
+
# `true` when sent asynchronously, otherwise the command's result.
|
|
60
|
+
#
|
|
61
|
+
def command(method, async: false, timeout: nil, **params)
|
|
23
62
|
message = build_message(method, params)
|
|
24
|
-
@client.send_message(message, async: async)
|
|
63
|
+
@client.send_message(message, async: async, timeout: timeout)
|
|
25
64
|
end
|
|
26
65
|
|
|
66
|
+
#
|
|
67
|
+
# Subscribes to a CDP event scoped to this session.
|
|
68
|
+
#
|
|
69
|
+
# @param [String] event
|
|
70
|
+
# The CDP event name.
|
|
71
|
+
#
|
|
72
|
+
# @return [Integer]
|
|
73
|
+
# The subscription id, used to unsubscribe via {#off}.
|
|
74
|
+
#
|
|
27
75
|
def on(event, &)
|
|
28
76
|
@client.on(event_name(event), &)
|
|
29
77
|
end
|
|
30
78
|
|
|
79
|
+
#
|
|
80
|
+
# Unsubscribes from a CDP event scoped to this session.
|
|
81
|
+
#
|
|
82
|
+
# @param [String] event
|
|
83
|
+
# The CDP event name.
|
|
84
|
+
#
|
|
85
|
+
# @param [Integer] id
|
|
86
|
+
# The subscription id returned by {#on}.
|
|
87
|
+
#
|
|
88
|
+
# @return [void]
|
|
89
|
+
#
|
|
31
90
|
def off(event, id)
|
|
32
91
|
@client.off(event_name(event), id)
|
|
33
92
|
end
|
|
34
93
|
|
|
94
|
+
# Whether there's at least one callback registered for the event, scoped
|
|
95
|
+
# to this session.
|
|
96
|
+
#
|
|
97
|
+
# @param [String] event
|
|
98
|
+
# The CDP event name.
|
|
99
|
+
#
|
|
100
|
+
# @return [Boolean]
|
|
35
101
|
def subscribed?(event)
|
|
36
102
|
@client.subscribed?(event_name(event))
|
|
37
103
|
end
|
|
38
104
|
|
|
105
|
+
# Supports {#method_missing} delegation by reporting the underlying
|
|
106
|
+
# {Client}'s methods as responded to.
|
|
107
|
+
#
|
|
108
|
+
# @return [Boolean]
|
|
39
109
|
def respond_to_missing?(name, include_private)
|
|
40
110
|
@client.respond_to?(name, include_private)
|
|
41
111
|
end
|
|
42
112
|
|
|
113
|
+
#
|
|
114
|
+
# Delegates any method not defined on `SessionClient` to the underlying
|
|
115
|
+
# {Client}, e.g. `subscribed?`.
|
|
116
|
+
#
|
|
117
|
+
# @return [untyped]
|
|
118
|
+
#
|
|
43
119
|
def method_missing(name, ...)
|
|
44
120
|
@client.send(name, ...)
|
|
45
121
|
end
|
|
46
122
|
|
|
123
|
+
#
|
|
124
|
+
# Removes all event callbacks registered for this session from the
|
|
125
|
+
# underlying client's subscriber.
|
|
126
|
+
#
|
|
127
|
+
# @return [void]
|
|
128
|
+
#
|
|
47
129
|
def close
|
|
48
130
|
@client.subscriber.clear(session_id: session_id)
|
|
49
131
|
end
|
|
@@ -59,15 +141,22 @@ module Ferrum
|
|
|
59
141
|
end
|
|
60
142
|
end
|
|
61
143
|
|
|
144
|
+
#
|
|
145
|
+
# The low-level CDP client. Owns the {WebSocket} connection to the browser,
|
|
146
|
+
# assigns command ids, matches responses back to their pending commands and
|
|
147
|
+
# dispatches incoming events to the {Subscriber}. {SessionClient} builds on
|
|
148
|
+
# top of it to scope commands/events to a particular target's session.
|
|
149
|
+
#
|
|
62
150
|
class Client
|
|
63
151
|
extend Forwardable
|
|
64
152
|
|
|
65
|
-
delegate %i[
|
|
153
|
+
delegate %i[protocol_timeout protocol_timeout=] => :options
|
|
66
154
|
|
|
67
155
|
attr_reader :ws_url, :options, :subscriber
|
|
68
156
|
|
|
69
157
|
def initialize(ws_url, options)
|
|
70
158
|
@command_id = 0
|
|
159
|
+
@command_id_mutex = Mutex.new
|
|
71
160
|
@ws_url = ws_url
|
|
72
161
|
@options = options
|
|
73
162
|
@pendings = Concurrent::Hash.new
|
|
@@ -77,12 +166,55 @@ module Ferrum
|
|
|
77
166
|
start
|
|
78
167
|
end
|
|
79
168
|
|
|
80
|
-
|
|
169
|
+
#
|
|
170
|
+
# Sends a CDP command to the browser-wide session.
|
|
171
|
+
#
|
|
172
|
+
# @param [String] method
|
|
173
|
+
# The CDP method name, e.g. `"Target.createTarget"`.
|
|
174
|
+
#
|
|
175
|
+
# @param [Boolean] async
|
|
176
|
+
# Whether to send the command without waiting for a response.
|
|
177
|
+
#
|
|
178
|
+
# @param [Hash] params
|
|
179
|
+
# The command's parameters.
|
|
180
|
+
#
|
|
181
|
+
# @param [Numeric, nil] timeout
|
|
182
|
+
# How long to wait for this command's response, overriding
|
|
183
|
+
# {Browser::Options#protocol_timeout}. See {#send_message}.
|
|
184
|
+
#
|
|
185
|
+
# @return [Boolean, Hash]
|
|
186
|
+
# `true` when sent asynchronously, otherwise the command's result.
|
|
187
|
+
#
|
|
188
|
+
def command(method, async: false, timeout: nil, **params)
|
|
81
189
|
message = build_message(method, params)
|
|
82
|
-
send_message(message, async: async)
|
|
190
|
+
send_message(message, async: async, timeout: timeout)
|
|
83
191
|
end
|
|
84
192
|
|
|
85
|
-
|
|
193
|
+
#
|
|
194
|
+
# Sends a raw CDP message over the websocket. Synchronous calls block
|
|
195
|
+
# until a matching response arrives, or `timeout` elapses, defaulting to
|
|
196
|
+
# `protocol_timeout` (delegated to {Browser::Options#protocol_timeout}).
|
|
197
|
+
# That default is the transport-level budget for internal CDP bookkeeping
|
|
198
|
+
# (e.g. `Target.createTarget`). {Page#command} overrides
|
|
199
|
+
# this back to `timeout`, or a caller-supplied budget (e.g. `#pdf`/
|
|
200
|
+
# `#screenshot`'s own `timeout:` argument), for the user-facing commands
|
|
201
|
+
# it issues -- some of which (e.g. `Page.navigate`, `Page.printToPDF`)
|
|
202
|
+
# rely on their own response latency to detect a stuck operation.
|
|
203
|
+
#
|
|
204
|
+
# @param [Hash] message
|
|
205
|
+
# The message to send, must include an `:id` key.
|
|
206
|
+
#
|
|
207
|
+
# @param [Boolean] async
|
|
208
|
+
# Whether to return immediately instead of waiting for a response.
|
|
209
|
+
#
|
|
210
|
+
# @param [Numeric, nil] timeout
|
|
211
|
+
# How long to wait for the response. Defaults to `protocol_timeout`.
|
|
212
|
+
#
|
|
213
|
+
# @return [Boolean, Hash]
|
|
214
|
+
# `true` when sent asynchronously, otherwise the parsed `"result"`
|
|
215
|
+
# from the response.
|
|
216
|
+
#
|
|
217
|
+
def send_message(message, async:, timeout: nil)
|
|
86
218
|
if async
|
|
87
219
|
@ws.send_message(message)
|
|
88
220
|
true
|
|
@@ -90,7 +222,7 @@ module Ferrum
|
|
|
90
222
|
pending = Concurrent::IVar.new
|
|
91
223
|
@pendings[message[:id]] = pending
|
|
92
224
|
@ws.send_message(message)
|
|
93
|
-
data = pending.value!(timeout)
|
|
225
|
+
data = pending.value!(timeout || protocol_timeout)
|
|
94
226
|
@pendings.delete(message[:id])
|
|
95
227
|
|
|
96
228
|
raise DeadBrowserError if data.nil? && @ws.messages.closed?
|
|
@@ -102,22 +234,63 @@ module Ferrum
|
|
|
102
234
|
end
|
|
103
235
|
end
|
|
104
236
|
|
|
237
|
+
#
|
|
238
|
+
# Subscribes to a CDP event.
|
|
239
|
+
#
|
|
240
|
+
# @param [String] event
|
|
241
|
+
# The CDP event name.
|
|
242
|
+
#
|
|
243
|
+
# @return [Integer]
|
|
244
|
+
# The subscription id, used to unsubscribe via {#off}.
|
|
245
|
+
#
|
|
105
246
|
def on(event, &)
|
|
106
247
|
@subscriber.on(event, &)
|
|
107
248
|
end
|
|
108
249
|
|
|
250
|
+
#
|
|
251
|
+
# Unsubscribes from a CDP event.
|
|
252
|
+
#
|
|
253
|
+
# @param [String] event
|
|
254
|
+
# The CDP event name.
|
|
255
|
+
#
|
|
256
|
+
# @param [Integer] id
|
|
257
|
+
# The subscription id returned by {#on}.
|
|
258
|
+
#
|
|
259
|
+
# @return [void]
|
|
260
|
+
#
|
|
109
261
|
def off(event, id)
|
|
110
262
|
@subscriber.off(event, id)
|
|
111
263
|
end
|
|
112
264
|
|
|
265
|
+
# Whether there's at least one callback registered for the event.
|
|
266
|
+
#
|
|
267
|
+
# @param [String] event
|
|
268
|
+
# The CDP event name.
|
|
269
|
+
#
|
|
270
|
+
# @return [Boolean]
|
|
113
271
|
def subscribed?(event)
|
|
114
272
|
@subscriber.subscribed?(event)
|
|
115
273
|
end
|
|
116
274
|
|
|
275
|
+
#
|
|
276
|
+
# Builds a client scoped to a given CDP session, e.g. a browsing
|
|
277
|
+
# context created via `Target.attachToTarget`.
|
|
278
|
+
#
|
|
279
|
+
# @param [String] session_id
|
|
280
|
+
# The CDP session id to scope commands and events to.
|
|
281
|
+
#
|
|
282
|
+
# @return [SessionClient]
|
|
283
|
+
#
|
|
117
284
|
def session(session_id)
|
|
118
285
|
SessionClient.new(self, session_id)
|
|
119
286
|
end
|
|
120
287
|
|
|
288
|
+
#
|
|
289
|
+
# Closes the underlying websocket, drops pending commands and stops
|
|
290
|
+
# the message-processing thread and subscriber.
|
|
291
|
+
#
|
|
292
|
+
# @return [void]
|
|
293
|
+
#
|
|
121
294
|
def close
|
|
122
295
|
@ws.close
|
|
123
296
|
# Give a thread some time to handle a tail of messages
|
|
@@ -126,6 +299,11 @@ module Ferrum
|
|
|
126
299
|
@subscriber.close
|
|
127
300
|
end
|
|
128
301
|
|
|
302
|
+
#
|
|
303
|
+
# Custom inspection that exposes internal state useful for debugging.
|
|
304
|
+
#
|
|
305
|
+
# @return [String]
|
|
306
|
+
#
|
|
129
307
|
def inspect
|
|
130
308
|
"#<#{self.class} " \
|
|
131
309
|
"@command_id=#{@command_id.inspect} " \
|
|
@@ -133,6 +311,17 @@ module Ferrum
|
|
|
133
311
|
"@ws=#{@ws.inspect}>"
|
|
134
312
|
end
|
|
135
313
|
|
|
314
|
+
#
|
|
315
|
+
# Builds a CDP message hash with a fresh, thread-safe command id.
|
|
316
|
+
#
|
|
317
|
+
# @param [String] method
|
|
318
|
+
# The CDP method name.
|
|
319
|
+
#
|
|
320
|
+
# @param [Hash] params
|
|
321
|
+
# The command's parameters.
|
|
322
|
+
#
|
|
323
|
+
# @return [Hash]
|
|
324
|
+
#
|
|
136
325
|
def build_message(method, params)
|
|
137
326
|
{ method: method, params: params }.merge(id: next_command_id)
|
|
138
327
|
end
|
|
@@ -154,8 +343,10 @@ module Ferrum
|
|
|
154
343
|
end
|
|
155
344
|
end
|
|
156
345
|
|
|
346
|
+
# Locked so two concurrent commands never share an id and read each
|
|
347
|
+
# other's responses from @pendings.
|
|
157
348
|
def next_command_id
|
|
158
|
-
@command_id += 1
|
|
349
|
+
@command_id_mutex.synchronize { @command_id += 1 }
|
|
159
350
|
end
|
|
160
351
|
|
|
161
352
|
def raise_browser_error(error)
|
data/lib/ferrum/context.rb
CHANGED
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
require "ferrum/target"
|
|
4
4
|
|
|
5
5
|
module Ferrum
|
|
6
|
+
#
|
|
7
|
+
# Represents a browser context, i.e. an isolated browsing profile (similar
|
|
8
|
+
# to an incognito window) with its own cookies, cache and storage. Keeps
|
|
9
|
+
# track of the {Target}s that belong to it and connects to them as {Page}s
|
|
10
|
+
# or {Worker}s. Managed by {Contexts}, which owns the browser's collection
|
|
11
|
+
# of contexts and routes CDP target events to the right one.
|
|
12
|
+
#
|
|
6
13
|
class Context
|
|
7
14
|
POSITION = %i[first last].freeze
|
|
8
15
|
|
|
@@ -16,16 +23,43 @@ module Ferrum
|
|
|
16
23
|
@pendings = Concurrent::Map.new
|
|
17
24
|
end
|
|
18
25
|
|
|
26
|
+
# The context's first known target, creating one via
|
|
27
|
+
# `Target.createTarget` if none has attached yet.
|
|
28
|
+
#
|
|
29
|
+
# @return [Target]
|
|
19
30
|
def default_target
|
|
20
31
|
@default_target ||= create_target
|
|
21
32
|
end
|
|
22
33
|
|
|
34
|
+
# Connects to and returns the {#default_target}'s page.
|
|
35
|
+
#
|
|
36
|
+
# @return [Page]
|
|
23
37
|
def page
|
|
24
38
|
default_target.page
|
|
25
39
|
end
|
|
26
40
|
|
|
41
|
+
# All page targets in this context, connected to as {Page}s.
|
|
42
|
+
#
|
|
43
|
+
# @return [Array<Page>]
|
|
27
44
|
def pages
|
|
28
|
-
@targets.values.
|
|
45
|
+
@targets.values.select(&:page?).map(&:page)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Dedicated and shared workers spawned by any page in this context.
|
|
49
|
+
#
|
|
50
|
+
# @return [Array<Worker>]
|
|
51
|
+
def workers
|
|
52
|
+
@targets.values.select { |t| t.worker? || t.shared_worker? }.map(&:worker)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Service worker targets registered in this context. Unlike {#workers},
|
|
56
|
+
# these are plain {Target}s and are not connected to. Attaching to a
|
|
57
|
+
# service worker's session keeps it alive indefinitely, so we only do
|
|
58
|
+
# that on demand, via `target.worker`.
|
|
59
|
+
#
|
|
60
|
+
# @return [Array<Target>]
|
|
61
|
+
def service_workers
|
|
62
|
+
@targets.values.select(&:service_worker?)
|
|
29
63
|
end
|
|
30
64
|
|
|
31
65
|
# When we call `page` method on target it triggers ruby to connect to given
|
|
@@ -40,23 +74,45 @@ module Ferrum
|
|
|
40
74
|
windows.map(&:page)
|
|
41
75
|
end
|
|
42
76
|
|
|
77
|
+
# Creates a new target in this context and eagerly connects to it as a
|
|
78
|
+
# {Page}, forwarding `options` to {Target#build_page}.
|
|
79
|
+
#
|
|
80
|
+
# @param [Hash] options
|
|
81
|
+
#
|
|
82
|
+
# @return [Page]
|
|
43
83
|
def create_page(**options)
|
|
44
84
|
target = create_target
|
|
45
85
|
target.page = target.build_page(**options)
|
|
46
86
|
end
|
|
47
87
|
|
|
88
|
+
# Creates a new target in this context via `Target.createTarget` and
|
|
89
|
+
# blocks until it's been registered (see {#add_target}).
|
|
90
|
+
#
|
|
91
|
+
# @return [Target]
|
|
92
|
+
#
|
|
93
|
+
# @raise [NoSuchTargetError]
|
|
48
94
|
def create_target
|
|
49
95
|
target_id = @client.command("Target.createTarget", browserContextId: @id, url: "about:blank")["targetId"]
|
|
50
96
|
|
|
51
97
|
new_pending = Concurrent::IVar.new
|
|
52
98
|
pending = @pendings.put_if_absent(target_id, new_pending) || new_pending
|
|
53
|
-
resolved = pending.value(@client.
|
|
99
|
+
resolved = pending.value(@client.protocol_timeout)
|
|
54
100
|
raise NoSuchTargetError unless resolved
|
|
55
101
|
|
|
56
102
|
@pendings.delete(target_id)
|
|
57
103
|
@targets[target_id]
|
|
58
104
|
end
|
|
59
105
|
|
|
106
|
+
# Registers a target discovered via a CDP `Target.*` event, or updates
|
|
107
|
+
# the session id on one already known. Called by {Contexts} as targets
|
|
108
|
+
# are created/attached.
|
|
109
|
+
#
|
|
110
|
+
# @param [Hash] params
|
|
111
|
+
# The target's `targetInfo`.
|
|
112
|
+
#
|
|
113
|
+
# @param [String, nil] session_id
|
|
114
|
+
#
|
|
115
|
+
# @return [Target]
|
|
60
116
|
def add_target(params:, session_id: nil)
|
|
61
117
|
new_target = Target.new(@client, session_id, params)
|
|
62
118
|
# `put_if_absent` returns nil if added a new value or existing if there was one already
|
|
@@ -68,48 +124,85 @@ module Ferrum
|
|
|
68
124
|
new_pending = Concurrent::IVar.new
|
|
69
125
|
pending = @pendings.put_if_absent(target.id, new_pending) || new_pending
|
|
70
126
|
pending.try_set(true)
|
|
71
|
-
|
|
127
|
+
target
|
|
72
128
|
end
|
|
73
129
|
|
|
130
|
+
# Updates a known target's params, e.g. on `Target.targetInfoChanged`.
|
|
131
|
+
#
|
|
132
|
+
# @param [String] target_id
|
|
133
|
+
#
|
|
134
|
+
# @param [Hash] params
|
|
135
|
+
#
|
|
136
|
+
# @return [void]
|
|
74
137
|
def update_target(target_id, params)
|
|
75
138
|
@targets[target_id]&.update(params)
|
|
76
139
|
end
|
|
77
140
|
|
|
141
|
+
# Removes a target, e.g. on `Target.targetDestroyed`/`targetCrashed`.
|
|
142
|
+
#
|
|
143
|
+
# @param [String] target_id
|
|
144
|
+
#
|
|
145
|
+
# @return [Target, nil]
|
|
78
146
|
def delete_target(target_id)
|
|
79
147
|
@targets.delete(target_id)
|
|
80
148
|
end
|
|
81
149
|
|
|
150
|
+
# Manually attaches to a target, e.g. a service worker discovered via
|
|
151
|
+
# {#service_workers}. Once attached, `target.worker`/`target.page`
|
|
152
|
+
# returns a connected {Worker}/{Page} for it.
|
|
153
|
+
#
|
|
154
|
+
# Note: attaching to a service worker's session prevents Chrome from
|
|
155
|
+
# ever terminating it while the connection is open.
|
|
82
156
|
def attach_target(target_id)
|
|
83
157
|
target = @targets[target_id]
|
|
84
158
|
raise NoSuchTargetError unless target
|
|
85
159
|
|
|
160
|
+
@contexts.manually_attached(target_id)
|
|
86
161
|
session = @client.command("Target.attachToTarget", targetId: target_id, flatten: true)
|
|
87
162
|
target.session_id = session["sessionId"]
|
|
88
163
|
true
|
|
89
164
|
end
|
|
90
165
|
|
|
166
|
+
# Returns the first target for which the block returns truthy.
|
|
167
|
+
#
|
|
168
|
+
# @return [Target, nil]
|
|
91
169
|
def find_target
|
|
92
170
|
@targets.each_value { |t| return t if yield(t) }
|
|
93
171
|
|
|
94
172
|
nil
|
|
95
173
|
end
|
|
96
174
|
|
|
175
|
+
# Closes the WebSocket connection of every connected target, without
|
|
176
|
+
# disposing the targets themselves.
|
|
177
|
+
#
|
|
178
|
+
# @return [void]
|
|
97
179
|
def close_targets_connection
|
|
98
180
|
@targets.each_value do |target|
|
|
99
181
|
next unless target.connected?
|
|
100
182
|
|
|
101
|
-
target.
|
|
183
|
+
target.close_connection
|
|
102
184
|
end
|
|
103
185
|
end
|
|
104
186
|
|
|
187
|
+
# Disposes this browser context and all of its targets.
|
|
188
|
+
#
|
|
189
|
+
# @return [Boolean]
|
|
105
190
|
def dispose
|
|
106
191
|
@contexts.dispose(@id)
|
|
107
192
|
end
|
|
108
193
|
|
|
194
|
+
# Whether a target with the given id is known in this context.
|
|
195
|
+
#
|
|
196
|
+
# @param [String] target_id
|
|
197
|
+
#
|
|
198
|
+
# @return [Boolean]
|
|
109
199
|
def target?(target_id)
|
|
110
200
|
!!@targets[target_id]
|
|
111
201
|
end
|
|
112
202
|
|
|
203
|
+
# Debug representation of the context, including its known targets.
|
|
204
|
+
#
|
|
205
|
+
# @return [String]
|
|
113
206
|
def inspect
|
|
114
207
|
%(#<#{self.class} @id=#{@id.inspect} @targets=#{@targets.inspect} @default_target=#{@default_target.inspect}>)
|
|
115
208
|
end
|