puppeteer-ruby 0.41.0 → 0.43.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/CHANGELOG.md +9 -1
- data/docs/api_coverage.md +221 -206
- data/lib/puppeteer/browser.rb +109 -75
- data/lib/puppeteer/browser_connector.rb +67 -0
- data/lib/puppeteer/chrome_target_manager.rb +256 -0
- data/lib/puppeteer/connection.rb +16 -0
- data/lib/puppeteer/element_handle.rb +10 -22
- data/lib/puppeteer/events.rb +9 -0
- data/lib/puppeteer/firefox_target_manager.rb +158 -0
- data/lib/puppeteer/frame.rb +4 -0
- data/lib/puppeteer/frame_manager.rb +69 -38
- data/lib/puppeteer/launcher/chrome.rb +3 -56
- data/lib/puppeteer/launcher/firefox.rb +1 -55
- data/lib/puppeteer/lifecycle_watcher.rb +2 -1
- data/lib/puppeteer/page.rb +38 -29
- data/lib/puppeteer/puppeteer.rb +1 -1
- data/lib/puppeteer/remote_object.rb +4 -0
- data/lib/puppeteer/target.rb +14 -1
- data/lib/puppeteer/version.rb +1 -1
- data/lib/puppeteer.rb +3 -0
- data/puppeteer-ruby.gemspec +1 -1
- metadata +7 -4
data/lib/puppeteer/browser.rb
CHANGED
@@ -7,13 +7,15 @@ class Puppeteer::Browser
|
|
7
7
|
include Puppeteer::IfPresent
|
8
8
|
using Puppeteer::DefineAsyncMethod
|
9
9
|
|
10
|
+
# @param product [String|nil] 'chrome' or 'firefox'
|
10
11
|
# @param {!Puppeteer.Connection} connection
|
11
12
|
# @param {!Array<string>} contextIds
|
12
13
|
# @param {boolean} ignoreHTTPSErrors
|
13
14
|
# @param {?Puppeteer.Viewport} defaultViewport
|
14
15
|
# @param process [Puppeteer::BrowserRunner::BrowserProcess|NilClass]
|
15
16
|
# @param {function()=} closeCallback
|
16
|
-
def self.create(
|
17
|
+
def self.create(product:,
|
18
|
+
connection:,
|
17
19
|
context_ids:,
|
18
20
|
ignore_https_errors:,
|
19
21
|
default_viewport:,
|
@@ -22,6 +24,7 @@ class Puppeteer::Browser
|
|
22
24
|
target_filter_callback:,
|
23
25
|
is_page_target_callback:)
|
24
26
|
browser = Puppeteer::Browser.new(
|
27
|
+
product: product,
|
25
28
|
connection: connection,
|
26
29
|
context_ids: context_ids,
|
27
30
|
ignore_https_errors: ignore_https_errors,
|
@@ -31,17 +34,19 @@ class Puppeteer::Browser
|
|
31
34
|
target_filter_callback: target_filter_callback,
|
32
35
|
is_page_target_callback: is_page_target_callback,
|
33
36
|
)
|
34
|
-
|
37
|
+
browser.send(:attach)
|
35
38
|
browser
|
36
39
|
end
|
37
40
|
|
41
|
+
# @param product [String|nil] 'chrome' or 'firefox'
|
38
42
|
# @param {!Puppeteer.Connection} connection
|
39
43
|
# @param {!Array<string>} contextIds
|
40
44
|
# @param {boolean} ignoreHTTPSErrors
|
41
45
|
# @param {?Puppeteer.Viewport} defaultViewport
|
42
46
|
# @param {?Puppeteer.ChildProcess} process
|
43
47
|
# @param {(function():Promise)=} closeCallback
|
44
|
-
def initialize(
|
48
|
+
def initialize(product:,
|
49
|
+
connection:,
|
45
50
|
context_ids:,
|
46
51
|
ignore_https_errors:,
|
47
52
|
default_viewport:,
|
@@ -49,6 +54,7 @@ class Puppeteer::Browser
|
|
49
54
|
close_callback:,
|
50
55
|
target_filter_callback:,
|
51
56
|
is_page_target_callback:)
|
57
|
+
@product = product || 'chrome'
|
52
58
|
@ignore_https_errors = ignore_https_errors
|
53
59
|
@default_viewport = default_viewport
|
54
60
|
@process = process
|
@@ -56,20 +62,26 @@ class Puppeteer::Browser
|
|
56
62
|
@close_callback = close_callback
|
57
63
|
@target_filter_callback = target_filter_callback || method(:default_target_filter_callback)
|
58
64
|
@is_page_target_callback = is_page_target_callback || method(:default_is_page_target_callback)
|
59
|
-
|
60
65
|
@default_context = Puppeteer::BrowserContext.new(@connection, self, nil)
|
61
66
|
@contexts = {}
|
67
|
+
|
62
68
|
context_ids.each do |context_id|
|
63
69
|
@contexts[context_id] = Puppeteer::BrowserContext.new(@connection, self, context_id)
|
64
70
|
end
|
65
|
-
|
66
|
-
@
|
67
|
-
|
68
|
-
|
71
|
+
|
72
|
+
if @product == 'firefox'
|
73
|
+
@target_manager = Puppeteer::FirefoxTargetManager.new(
|
74
|
+
connection: connection,
|
75
|
+
target_factory: method(:create_target),
|
76
|
+
target_filter_callback: @target_filter_callback,
|
77
|
+
)
|
78
|
+
else
|
79
|
+
@target_manager = Puppeteer::ChromeTargetManager.new(
|
80
|
+
connection: connection,
|
81
|
+
target_factory: method(:create_target),
|
82
|
+
target_filter_callback: @target_filter_callback,
|
83
|
+
)
|
69
84
|
end
|
70
|
-
@connection.on_event('Target.targetCreated', &method(:handle_target_created))
|
71
|
-
@connection.on_event('Target.targetDestroyed', &method(:handle_target_destroyed))
|
72
|
-
@connection.on_event('Target.targetInfoChanged', &method(:handle_target_info_changed))
|
73
85
|
end
|
74
86
|
|
75
87
|
private def default_target_filter_callback(target_info)
|
@@ -100,11 +112,45 @@ class Puppeteer::Browser
|
|
100
112
|
super(event_name.to_s, &block)
|
101
113
|
end
|
102
114
|
|
115
|
+
private def attach
|
116
|
+
@connection_event_listeners ||= []
|
117
|
+
@connection_event_listeners << @connection.add_event_listener(ConnectionEmittedEvents::Disconnected) do
|
118
|
+
emit_event(BrowserEmittedEvents::Disconnected)
|
119
|
+
end
|
120
|
+
@target_manager_event_listeners ||= []
|
121
|
+
@target_manager.add_event_listener(
|
122
|
+
TargetManagerEmittedEvents::TargetAvailable,
|
123
|
+
&method(:handle_attached_to_target)
|
124
|
+
)
|
125
|
+
@target_manager.add_event_listener(
|
126
|
+
TargetManagerEmittedEvents::TargetGone,
|
127
|
+
&method(:handle_detached_from_target)
|
128
|
+
)
|
129
|
+
@target_manager.add_event_listener(
|
130
|
+
TargetManagerEmittedEvents::TargetChanged,
|
131
|
+
&method(:handle_target_changed)
|
132
|
+
)
|
133
|
+
@target_manager.add_event_listener(
|
134
|
+
TargetManagerEmittedEvents::TargetDiscovered,
|
135
|
+
&method(:handle_target_discovered)
|
136
|
+
)
|
137
|
+
@target_manager.init
|
138
|
+
end
|
139
|
+
|
140
|
+
private def detach
|
141
|
+
@connection.remove_event_listener(*@connection_event_listeners)
|
142
|
+
@target_manager.remove_event_listener(*@target_manager_event_listeners)
|
143
|
+
end
|
144
|
+
|
103
145
|
# @return [Puppeteer::BrowserRunner::BrowserProcess]
|
104
146
|
def process
|
105
147
|
@process
|
106
148
|
end
|
107
149
|
|
150
|
+
private def target_manager
|
151
|
+
@target_manager
|
152
|
+
end
|
153
|
+
|
108
154
|
# @return [Puppeteer::BrowserContext]
|
109
155
|
def create_incognito_browser_context
|
110
156
|
result = @connection.send_message('Target.createBrowserContext')
|
@@ -123,19 +169,16 @@ class Puppeteer::Browser
|
|
123
169
|
|
124
170
|
# @param context_id [String]
|
125
171
|
def dispose_context(context_id)
|
172
|
+
return unless context_id
|
126
173
|
@connection.send_message('Target.disposeBrowserContext', browserContextId: context_id)
|
127
174
|
@contexts.delete(context_id)
|
128
175
|
end
|
129
176
|
|
130
|
-
class
|
131
|
-
def initialize
|
132
|
-
super('Target should not exist before targetCreated')
|
133
|
-
end
|
134
|
-
end
|
177
|
+
class MissingBrowserContextError < StandardError ; end
|
135
178
|
|
136
|
-
# @param
|
137
|
-
|
138
|
-
|
179
|
+
# @param target_info [Puppeteer::Target::TargetInfo]
|
180
|
+
# @param session [CDPSession|nil]
|
181
|
+
def create_target(target_info, session)
|
139
182
|
browser_context_id = target_info.browser_context_id
|
140
183
|
context =
|
141
184
|
if browser_context_id && @contexts.has_key?(browser_context_id)
|
@@ -144,56 +187,39 @@ class Puppeteer::Browser
|
|
144
187
|
@default_context
|
145
188
|
end
|
146
189
|
|
147
|
-
|
148
|
-
raise
|
190
|
+
unless context
|
191
|
+
raise MissingBrowserContextError.new('Missing browser context')
|
149
192
|
end
|
150
193
|
|
151
|
-
|
152
|
-
|
153
|
-
target = Puppeteer::Target.new(
|
194
|
+
Puppeteer::Target.new(
|
154
195
|
target_info: target_info,
|
196
|
+
session: session,
|
155
197
|
browser_context: context,
|
198
|
+
target_manager: @target_manager,
|
156
199
|
session_factory: -> { @connection.create_session(target_info) },
|
157
200
|
ignore_https_errors: @ignore_https_errors,
|
158
201
|
default_viewport: @default_viewport,
|
159
202
|
is_page_target_callback: @is_page_target_callback,
|
160
203
|
)
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
if await target.initialized_promise
|
204
|
+
end
|
205
|
+
|
206
|
+
private def handle_attached_to_target(target)
|
207
|
+
if target.initialized_promise.value!
|
166
208
|
emit_event(BrowserEmittedEvents::TargetCreated, target)
|
167
|
-
|
209
|
+
target.browser_context.emit_event(BrowserContextEmittedEvents::TargetCreated, target)
|
168
210
|
end
|
169
211
|
end
|
170
212
|
|
171
|
-
|
172
|
-
def handle_target_destroyed(event)
|
173
|
-
target_id = event['targetId']
|
174
|
-
target = @targets[target_id]
|
213
|
+
private def handle_detached_from_target(target)
|
175
214
|
target.ignore_initialize_callback_promise
|
176
|
-
@targets.delete(target_id)
|
177
|
-
if_present(@wait_for_creating_targets.delete(target_id)) do |promise|
|
178
|
-
promise.reject('target destroyed')
|
179
|
-
end
|
180
215
|
target.closed_callback
|
181
|
-
if
|
216
|
+
if target.initialized_promise.value!
|
182
217
|
emit_event(BrowserEmittedEvents::TargetDestroyed, target)
|
183
218
|
target.browser_context.emit_event(BrowserContextEmittedEvents::TargetDestroyed, target)
|
184
219
|
end
|
185
220
|
end
|
186
221
|
|
187
|
-
|
188
|
-
def initialize
|
189
|
-
super('target should exist before targetInfoChanged')
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
# @param {!Protocol.Target.targetInfoChangedPayload} event
|
194
|
-
def handle_target_info_changed(event)
|
195
|
-
target_info = Puppeteer::Target::TargetInfo.new(event['targetInfo'])
|
196
|
-
target = @targets[target_info.target_id] or raise TargetNotExistError.new
|
222
|
+
private def handle_target_changed(target, target_info)
|
197
223
|
previous_url = target.url
|
198
224
|
was_initialized = target.initialized?
|
199
225
|
target.handle_target_info_changed(target_info)
|
@@ -203,6 +229,10 @@ class Puppeteer::Browser
|
|
203
229
|
end
|
204
230
|
end
|
205
231
|
|
232
|
+
private def handle_target_discovered(target_info)
|
233
|
+
emit_event('targetdiscovered', target_info)
|
234
|
+
end
|
235
|
+
|
206
236
|
# @return [String]
|
207
237
|
def ws_endpoint
|
208
238
|
@connection.url
|
@@ -212,44 +242,47 @@ class Puppeteer::Browser
|
|
212
242
|
@default_context.new_page
|
213
243
|
end
|
214
244
|
|
245
|
+
class MissingTargetError < StandardError ; end
|
246
|
+
class CreatePageError < StandardError ; end
|
247
|
+
|
215
248
|
# @param {?string} contextId
|
216
249
|
# @return {!Promise<!Puppeteer.Page>}
|
217
250
|
def create_page_in_context(context_id)
|
218
|
-
create_target_params = {
|
219
|
-
|
220
|
-
|
221
|
-
|
251
|
+
create_target_params = {
|
252
|
+
url: 'about:blank',
|
253
|
+
browserContextId: context_id,
|
254
|
+
}.compact
|
222
255
|
result = @connection.send_message('Target.createTarget', **create_target_params)
|
223
256
|
target_id = result['targetId']
|
224
|
-
target = @
|
257
|
+
target = @target_manager.available_targets[target_id]
|
225
258
|
unless target
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
259
|
+
raise MissingTargetError.new("Missing target for page (id = #{target_id})")
|
260
|
+
end
|
261
|
+
unless target.initialized_promise.value!
|
262
|
+
raise CreatePageError.new("Failed to create target for page (id = #{target_id})")
|
263
|
+
end
|
264
|
+
page = target.page
|
265
|
+
unless page
|
266
|
+
raise CreatePageError.new("Failed to create a page for context (id = #{context_id})")
|
234
267
|
end
|
235
|
-
|
236
|
-
await target.page
|
268
|
+
page
|
237
269
|
end
|
238
270
|
|
239
|
-
#
|
271
|
+
# All active targets inside the Browser. In case of multiple browser contexts, returns
|
272
|
+
# an array with all the targets in all browser contexts.
|
240
273
|
def targets
|
241
|
-
@
|
274
|
+
@target_manager.available_targets.values.select { |target| target.initialized? }
|
242
275
|
end
|
243
276
|
|
244
277
|
|
245
|
-
#
|
278
|
+
# The target associated with the browser.
|
246
279
|
def target
|
247
|
-
targets.find { |target| target.type == 'browser' }
|
280
|
+
targets.find { |target| target.type == 'browser' } or raise 'Browser target is not found'
|
248
281
|
end
|
249
282
|
|
250
283
|
# used only in Target#opener
|
251
284
|
private def find_target_by_id(target_id)
|
252
|
-
@
|
285
|
+
@target_manager.available_targets[target_id]
|
253
286
|
end
|
254
287
|
|
255
288
|
# @param predicate [Proc(Puppeteer::Target -> Boolean)]
|
@@ -293,12 +326,12 @@ class Puppeteer::Browser
|
|
293
326
|
|
294
327
|
# @return [String]
|
295
328
|
def version
|
296
|
-
|
329
|
+
Version.fetch(@connection).product
|
297
330
|
end
|
298
331
|
|
299
332
|
# @return [String]
|
300
333
|
def user_agent
|
301
|
-
|
334
|
+
Version.fetch(@connection).user_agent
|
302
335
|
end
|
303
336
|
|
304
337
|
def close
|
@@ -307,6 +340,7 @@ class Puppeteer::Browser
|
|
307
340
|
end
|
308
341
|
|
309
342
|
def disconnect
|
343
|
+
@target_manager.dispose
|
310
344
|
@connection.dispose
|
311
345
|
end
|
312
346
|
|
@@ -315,6 +349,10 @@ class Puppeteer::Browser
|
|
315
349
|
end
|
316
350
|
|
317
351
|
class Version
|
352
|
+
def self.fetch(connection)
|
353
|
+
new(connection.send_message('Browser.getVersion'))
|
354
|
+
end
|
355
|
+
|
318
356
|
def initialize(hash)
|
319
357
|
@protocol_version = hash['protocolVersion']
|
320
358
|
@product = hash['product']
|
@@ -325,8 +363,4 @@ class Puppeteer::Browser
|
|
325
363
|
|
326
364
|
attr_reader :protocol_version, :product, :revision, :user_agent, :js_version
|
327
365
|
end
|
328
|
-
|
329
|
-
private def get_version
|
330
|
-
Version.new(@connection.send_message('Browser.getVersion'))
|
331
|
-
end
|
332
366
|
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require_relative './browser'
|
2
|
+
require_relative './launcher/browser_options'
|
3
|
+
|
4
|
+
class Puppeteer::BrowserConnector
|
5
|
+
def initialize(options)
|
6
|
+
@browser_options = Puppeteer::Launcher::BrowserOptions.new(options)
|
7
|
+
@browser_ws_endpoint = options[:browser_ws_endpoint]
|
8
|
+
@browser_url = options[:browser_url]
|
9
|
+
@transport = options[:transport]
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [Puppeteer::Browser]
|
13
|
+
def connect_to_browser
|
14
|
+
version = Puppeteer::Browser::Version.fetch(connection)
|
15
|
+
product = version.product.downcase.include?('firefox') ? 'firefox' : 'chrome'
|
16
|
+
|
17
|
+
result = connection.send_message('Target.getBrowserContexts')
|
18
|
+
browser_context_ids = result['browserContextIds']
|
19
|
+
|
20
|
+
Puppeteer::Browser.create(
|
21
|
+
product: product,
|
22
|
+
connection: connection,
|
23
|
+
context_ids: browser_context_ids,
|
24
|
+
ignore_https_errors: @browser_options.ignore_https_errors?,
|
25
|
+
default_viewport: @browser_options.default_viewport,
|
26
|
+
process: nil,
|
27
|
+
close_callback: -> { connection.send_message('Browser.close') },
|
28
|
+
target_filter_callback: @browser_options.target_filter,
|
29
|
+
is_page_target_callback: @browser_options.is_page_target,
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
private def connection
|
34
|
+
@connection ||=
|
35
|
+
if @browser_ws_endpoint && @browser_url.nil? && @transport.nil?
|
36
|
+
connect_with_browser_ws_endpoint(@browser_ws_endpoint)
|
37
|
+
elsif @browser_ws_endpoint.nil? && @browser_url && @transport.nil?
|
38
|
+
connect_with_browser_url(@browser_url)
|
39
|
+
elsif @browser_ws_endpoint.nil? && @browser_url.nil? && @transport
|
40
|
+
connect_with_transport(@transport)
|
41
|
+
else
|
42
|
+
raise ArgumentError.new("Exactly one of browserWSEndpoint, browserURL or transport must be passed to puppeteer.connect")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# @return [Puppeteer::Connection]
|
47
|
+
private def connect_with_browser_ws_endpoint(browser_ws_endpoint)
|
48
|
+
transport = Puppeteer::WebSocketTransport.create(browser_ws_endpoint)
|
49
|
+
Puppeteer::Connection.new(browser_ws_endpoint, transport, @browser_options.slow_mo)
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [Puppeteer::Connection]
|
53
|
+
private def connect_with_browser_url(browser_url)
|
54
|
+
require 'net/http'
|
55
|
+
uri = URI(browser_url)
|
56
|
+
uri.path = '/json/version'
|
57
|
+
response_body = Net::HTTP.get(uri)
|
58
|
+
json = JSON.parse(response_body)
|
59
|
+
connection_url = json['webSocketDebuggerUrl']
|
60
|
+
connect_with_browser_ws_endpoint(connection_url)
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return [Puppeteer::Connection]
|
64
|
+
private def connect_with_transport(transport)
|
65
|
+
Puppeteer::Connection.new('', transport, @browser_options.slow_mo)
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,256 @@
|
|
1
|
+
class Puppeteer::ChromeTargetManager
|
2
|
+
include Puppeteer::EventCallbackable
|
3
|
+
|
4
|
+
def initialize(connection:, target_factory:, target_filter_callback:)
|
5
|
+
@discovered_targets_by_target_id = {}
|
6
|
+
@attached_targets_by_target_id = {}
|
7
|
+
@attached_targets_by_session_id = {}
|
8
|
+
@ignored_targets = Set.new
|
9
|
+
@target_ids_for_init = Set.new
|
10
|
+
|
11
|
+
@connection = connection
|
12
|
+
@target_filter_callback = target_filter_callback
|
13
|
+
@target_factory = target_factory
|
14
|
+
@target_interceptors = {}
|
15
|
+
@initialize_promise = resolvable_future
|
16
|
+
|
17
|
+
@connection_event_listeners = []
|
18
|
+
@connection_event_listeners << @connection.add_event_listener(
|
19
|
+
'Target.targetCreated',
|
20
|
+
&method(:handle_target_created)
|
21
|
+
)
|
22
|
+
@connection_event_listeners << @connection.add_event_listener(
|
23
|
+
'Target.targetDestroyed',
|
24
|
+
&method(:handle_target_destroyed)
|
25
|
+
)
|
26
|
+
@connection_event_listeners << @connection.add_event_listener(
|
27
|
+
'Target.targetInfoChanged',
|
28
|
+
&method(:handle_target_info_changed)
|
29
|
+
)
|
30
|
+
@connection_event_listeners << @connection.add_event_listener(
|
31
|
+
'sessiondetached',
|
32
|
+
&method(:handle_session_detached)
|
33
|
+
)
|
34
|
+
|
35
|
+
setup_attachment_listeners(@connection)
|
36
|
+
@connection.async_send_message('Target.setDiscoverTargets', discover: true)
|
37
|
+
end
|
38
|
+
|
39
|
+
def init
|
40
|
+
@discovered_targets_by_target_id.each do |target_id, target_info|
|
41
|
+
if @target_filter_callback.call(target_info)
|
42
|
+
@target_ids_for_init << target_id
|
43
|
+
end
|
44
|
+
end
|
45
|
+
@connection.send_message('Target.setAutoAttach', {
|
46
|
+
waitForDebuggerOnStart: true,
|
47
|
+
flatten: true,
|
48
|
+
autoAttach: true,
|
49
|
+
})
|
50
|
+
@initialize_promise.value!
|
51
|
+
end
|
52
|
+
|
53
|
+
def dispose
|
54
|
+
@connection.remove_event_listener(*@connection_event_listeners)
|
55
|
+
remove_attachment_listeners(@connection)
|
56
|
+
end
|
57
|
+
|
58
|
+
def available_targets
|
59
|
+
@attached_targets_by_target_id
|
60
|
+
end
|
61
|
+
|
62
|
+
def add_target_interceptor(client, interceptor)
|
63
|
+
interceptors = @target_interceptors[client] || []
|
64
|
+
interceptors << interceptor
|
65
|
+
@target_interceptors[client] = interceptors
|
66
|
+
end
|
67
|
+
|
68
|
+
def remove_target_interceptor(client, interceptor)
|
69
|
+
@target_interceptors[client]&.delete_if { |current| current == interceptor }
|
70
|
+
end
|
71
|
+
|
72
|
+
private def setup_attachment_listeners(session)
|
73
|
+
@attachment_listener_ids ||= {}
|
74
|
+
@attachment_listener_ids[session] ||= []
|
75
|
+
|
76
|
+
@attachment_listener_ids[session] << session.add_event_listener('Target.attachedToTarget') do |event|
|
77
|
+
handle_attached_to_target(session, event)
|
78
|
+
end
|
79
|
+
|
80
|
+
@attachment_listener_ids[session] << session.add_event_listener('Target.detachedFromTarget') do |event|
|
81
|
+
handle_detached_from_target(session, event)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
private def remove_attachment_listeners(session)
|
86
|
+
return unless @attachment_listener_ids
|
87
|
+
listener_ids = @attachment_listener_ids.delete(session)
|
88
|
+
return if !listener_ids || listener_ids.empty?
|
89
|
+
session.remove_event_listener(*listener_ids)
|
90
|
+
end
|
91
|
+
|
92
|
+
private def handle_session_detached(session)
|
93
|
+
remove_attachment_listeners(session)
|
94
|
+
@target_interceptors.delete(session)
|
95
|
+
end
|
96
|
+
|
97
|
+
private def handle_target_created(event)
|
98
|
+
target_info = Puppeteer::Target::TargetInfo.new(event['targetInfo'])
|
99
|
+
@discovered_targets_by_target_id[target_info.target_id] = target_info
|
100
|
+
|
101
|
+
emit_event(TargetManagerEmittedEvents::TargetDiscovered, target_info)
|
102
|
+
|
103
|
+
# The connection is already attached to the browser target implicitly,
|
104
|
+
# therefore, no new CDPSession is created and we have special handling
|
105
|
+
# here.
|
106
|
+
if target_info.type == 'browser' && target_info.attached
|
107
|
+
return if @attached_targets_by_target_id[target_info.target_id]
|
108
|
+
|
109
|
+
target = @target_factory.call(target_info, nil)
|
110
|
+
@attached_targets_by_target_id[target_info.target_id] = target
|
111
|
+
end
|
112
|
+
|
113
|
+
if target_info.type == 'shared_worker'
|
114
|
+
# Special case (https://crbug.com/1338156): currently, shared_workers
|
115
|
+
# don't get auto-attached. This should be removed once the auto-attach
|
116
|
+
# works.
|
117
|
+
@connection.create_session(target_info)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
private def handle_target_destroyed(event)
|
122
|
+
target_id = event['targetId']
|
123
|
+
target_info = @discovered_targets_by_target_id.delete(target_id)
|
124
|
+
finish_initialization_if_ready(target_id)
|
125
|
+
|
126
|
+
if target_info.type == 'service_worker' && @attached_targets_by_target_id.has_key?(target_id)
|
127
|
+
# Special case for service workers: report TargetGone event when
|
128
|
+
# the worker is destroyed.
|
129
|
+
target = @attached_targets_by_target_id.delete(target_id)
|
130
|
+
emit_event(TargetManagerEmittedEvents::TargetGone, target)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
private def handle_target_info_changed(event)
|
135
|
+
target_info = Puppeteer::Target::TargetInfo.new(event['targetInfo'])
|
136
|
+
@discovered_targets_by_target_id[target_info.target_id] = target_info
|
137
|
+
|
138
|
+
if @ignored_targets.include?(target_info.target_id) || !@attached_targets_by_target_id.has_key?(target_info.target_id) || !target_info.attached
|
139
|
+
return
|
140
|
+
end
|
141
|
+
original_target = @attached_targets_by_target_id[target_info.target_id]
|
142
|
+
emit_event(TargetManagerEmittedEvents::TargetChanged, original_target, target_info)
|
143
|
+
end
|
144
|
+
|
145
|
+
class SessionNotCreatedError < StandardError ; end
|
146
|
+
|
147
|
+
private def handle_attached_to_target(parent_session, event)
|
148
|
+
target_info = Puppeteer::Target::TargetInfo.new(event['targetInfo'])
|
149
|
+
session_id = event['sessionId']
|
150
|
+
session = @connection.session(session_id)
|
151
|
+
unless session
|
152
|
+
raise SessionNotCreatedError.new("Session #{session_id} was not created.")
|
153
|
+
end
|
154
|
+
|
155
|
+
silent_detach = -> {
|
156
|
+
begin
|
157
|
+
session.send_message('Runtime.runIfWaitingForDebugger')
|
158
|
+
rescue => err
|
159
|
+
Logger.new($stderr).warn(err)
|
160
|
+
end
|
161
|
+
|
162
|
+
# We don't use `session.detach()` because that dispatches all commands on
|
163
|
+
# the connection instead of the parent session.
|
164
|
+
begin
|
165
|
+
parent_session.send_message('Target.detachFromTarget', {
|
166
|
+
sessionId: session.id,
|
167
|
+
})
|
168
|
+
rescue => err
|
169
|
+
Logger.new($stderr).warn(err)
|
170
|
+
end
|
171
|
+
}
|
172
|
+
|
173
|
+
# Special case for service workers: being attached to service workers will
|
174
|
+
# prevent them from ever being destroyed. Therefore, we silently detach
|
175
|
+
# from service workers unless the connection was manually created via
|
176
|
+
# `page.worker()`. To determine this, we use
|
177
|
+
# `this.#connection.isAutoAttached(targetInfo.targetId)`. In the future, we
|
178
|
+
# should determine if a target is auto-attached or not with the help of
|
179
|
+
# CDP.
|
180
|
+
if target_info.type == 'service_worker' && @connection.auto_attached?(target_info.target_id)
|
181
|
+
finish_initialization_if_ready(target_info.target_id)
|
182
|
+
silent_detach.call
|
183
|
+
if parent_session.is_a?(Puppeteer::CDPSession)
|
184
|
+
target = @target_factory.call(target_info, parent_session)
|
185
|
+
@attached_targets_by_target_id[target_info.target_id] = target
|
186
|
+
emit_event(TargetManagerEmittedEvents::TargetAvailable, target)
|
187
|
+
end
|
188
|
+
|
189
|
+
return
|
190
|
+
end
|
191
|
+
|
192
|
+
unless @target_filter_callback.call(target_info)
|
193
|
+
@ignored_targets << target_info.target_id
|
194
|
+
finish_initialization_if_ready(target_info.target_id)
|
195
|
+
silent_detach.call
|
196
|
+
|
197
|
+
return
|
198
|
+
end
|
199
|
+
|
200
|
+
target = @attached_targets_by_target_id[target_info.target_id] || @target_factory.call(target_info, session)
|
201
|
+
setup_attachment_listeners(session)
|
202
|
+
|
203
|
+
@attached_targets_by_target_id[target_info.target_id] ||= target
|
204
|
+
@attached_targets_by_session_id[session.id] = target
|
205
|
+
|
206
|
+
@target_interceptors[parent_session]&.each do |interceptor|
|
207
|
+
if parent_session.is_a?(Puppeteer::Connection)
|
208
|
+
interceptor.call(target, nil)
|
209
|
+
else
|
210
|
+
# Sanity check: if parent session is not a connection, it should be
|
211
|
+
# present in #attachedTargetsBySessionId.
|
212
|
+
attached_target = @attached_targets_by_session_id[parent_session.id]
|
213
|
+
unless attached_target
|
214
|
+
raise "No target found for the parent session: #{parent_session.id}"
|
215
|
+
end
|
216
|
+
interceptor.call(target, attached_target)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
@target_ids_for_init.delete(target.target_id)
|
221
|
+
future { emit_event(TargetManagerEmittedEvents::TargetAvailable, target) }
|
222
|
+
|
223
|
+
if @target_ids_for_init.empty?
|
224
|
+
@initialize_promise.fulfill(nil) unless @initialize_promise.resolved?
|
225
|
+
end
|
226
|
+
|
227
|
+
future do
|
228
|
+
# TODO: the browser might be shutting down here. What do we do with the error?
|
229
|
+
await_all(
|
230
|
+
session.async_send_message('Target.setAutoAttach', {
|
231
|
+
waitForDebuggerOnStart: true,
|
232
|
+
flatten: true,
|
233
|
+
autoAttach: true,
|
234
|
+
}),
|
235
|
+
session.async_send_message('Runtime.runIfWaitingForDebugger'),
|
236
|
+
)
|
237
|
+
rescue => err
|
238
|
+
Logger.new($stderr).warn(err)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
private def finish_initialization_if_ready(target_id)
|
243
|
+
@target_ids_for_init.delete(target_id)
|
244
|
+
if @target_ids_for_init.empty?
|
245
|
+
@initialize_promise.fulfill(nil) unless @initialize_promise.resolved?
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
private def handle_detached_from_target(parent_session, event)
|
250
|
+
session_id = event['sessionId']
|
251
|
+
target = @attached_targets_by_session_id.delete(session_id)
|
252
|
+
return unless target
|
253
|
+
@attached_targets_by_target_id.delete(target.target_id)
|
254
|
+
emit_event(TargetManagerEmittedEvents::TargetGone, target)
|
255
|
+
end
|
256
|
+
end
|