puppeteer-ruby 0.37.4 → 0.38.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '000206932f5d3d4626602e984cff4015039644eea2ef89ff139b239fffb9f673'
4
- data.tar.gz: 0e560f862a3317a42c1e0e2907c599bc654d05e3f17fd5a650126b73bfd6948e
3
+ metadata.gz: d18f0aae5e331fee42f66c8140bdc3cced9c0514a03ab65460c4036a94bc6eb2
4
+ data.tar.gz: babeb261ac3661a6738eb3d02dddeef22685590791fbd579483edd2da7800606
5
5
  SHA512:
6
- metadata.gz: aac4be4a26f3116eee198ea65c0017359589f2ad11cc95125497e3f84ea1277d9bbb8d1071b57fb42812bb12afd4370534c48586fbb79897acafb54d03222159
7
- data.tar.gz: de51d1ca30e26e13346532118df225370fad4b1967d205dad0c150574c4fa60a649973aab83e24b2140b8f98f05626eb106a9141f04b94fe5d3927b1231737d4
6
+ metadata.gz: 0d70da4b6dc257f39d020286672180d93a4fe2aa321828035eeba80aeff92e7b1f28ff07acd15396d03aad33ab84b4f3474f1c75b587cb833d90f17dde4b024f
7
+ data.tar.gz: ccb8a58f1ffff28d1498c560c8f17671064ca00ce4b98af9dbc5e844ab1c77d9c28fc503c021ae019d9e799a287976fe01a88bbce3f87979d163f7d384336c0e
data/CHANGELOG.md CHANGED
@@ -1,7 +1,19 @@
1
- ### main [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.37.4...main)]
1
+ ### main [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.38.0...main)]
2
2
 
3
3
  - xxx
4
4
 
5
+ ### 0.38.0 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.37.4...0.38.0)]
6
+
7
+ New features:
8
+
9
+ - Puppeteer 11.0 functionalities
10
+ * OOP iframe support
11
+ * Customize debugging port
12
+ * HTTPRequest#initiator
13
+ * Customize temp directory for user data
14
+ * Add webp to screenshot quality option allow list
15
+
16
+
5
17
  ### 0.37.4 [[diff](https://github.com/YusukeIwaki/puppeteer-ruby/compare/0.37.3...0.37.4)]
6
18
 
7
19
  Bugfix:
data/docs/api_coverage.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # API coverages
2
2
  - Puppeteer version: v12.0.0
3
- - puppeteer-ruby version: 0.37.4
3
+ - puppeteer-ruby version: 0.38.0
4
4
 
5
5
  ## Puppeteer
6
6
 
@@ -134,7 +134,7 @@
134
134
  * viewport
135
135
  * ~~waitFor~~
136
136
  * waitForFileChooser => `#wait_for_file_chooser`
137
- * ~~waitForFrame~~
137
+ * waitForFrame => `#wait_for_frame`
138
138
  * waitForFunction => `#wait_for_function`
139
139
  * waitForNavigation => `#wait_for_navigation`
140
140
  * ~~waitForNetworkIdle~~
@@ -227,7 +227,7 @@
227
227
  * goto
228
228
  * hover
229
229
  * isDetached => `#detached?`
230
- * ~~isOOPFrame~~
230
+ * isOOPFrame => `#oop_frame?`
231
231
  * name
232
232
  * parentFrame => `#parent_frame`
233
233
  * select
@@ -308,7 +308,7 @@
308
308
  * finalizeInterceptions => `#finalize_interceptions`
309
309
  * frame
310
310
  * headers
311
- * ~~initiator~~
311
+ * initiator
312
312
  * isNavigationRequest => `#navigation_request?`
313
313
  * method
314
314
  * postData => `#post_data`
@@ -360,7 +360,7 @@
360
360
 
361
361
  * connection
362
362
  * detach
363
- * ~~id~~
363
+ * id
364
364
  * send
365
365
 
366
366
  ## Coverage
@@ -15,6 +15,11 @@ class Puppeteer::CDPSession
15
15
  @session_id = session_id
16
16
  end
17
17
 
18
+ # @internal
19
+ def id
20
+ @session_id
21
+ end
22
+
18
23
  attr_reader :connection
19
24
 
20
25
  # @param method [String]
@@ -45,10 +45,14 @@ class Puppeteer::DOMWorld
45
45
  end
46
46
  end
47
47
 
48
- # @param {!Puppeteer.FrameManager} frameManager
49
- # @param {!Puppeteer.Frame} frame
50
- # @param {!Puppeteer.TimeoutSettings} timeoutSettings
51
- def initialize(frame_manager, frame, timeout_settings)
48
+ # @param client [Puppeteer::CDPSession]
49
+ # @param frame_manager [Puppeteer::FrameManager]
50
+ # @param frame [Puppeteer::Frame]
51
+ # @param timeout_settings [Puppeteer::TimeoutSettings]
52
+ def initialize(client, frame_manager, frame, timeout_settings)
53
+ # Keep own reference to client because it might differ from the FrameManager's
54
+ # client for OOP iframes.
55
+ @client = client
52
56
  @frame_manager = frame_manager
53
57
  @frame = frame
54
58
  @timeout_settings = timeout_settings
@@ -58,7 +62,7 @@ class Puppeteer::DOMWorld
58
62
  @ctx_bindings = Set.new
59
63
  @detached = false
60
64
 
61
- frame_manager.client.on_event('Runtime.bindingCalled', &method(:handle_binding_called))
65
+ @client.on_event('Runtime.bindingCalled', &method(:handle_binding_called))
62
66
  end
63
67
 
64
68
  attr_reader :frame
@@ -1,25 +1,47 @@
1
1
  class Puppeteer::Frame
2
2
  using Puppeteer::DefineAsyncMethod
3
3
 
4
- # @param {!FrameManager} frameManager
5
- # @param {!Puppeteer.CDPSession} client
6
- # @param {?Frame} parentFrame
7
- # @param {string} frameId
8
- def initialize(frame_manager, client, parent_frame, frame_id)
4
+ # @param frame_manager [Puppeteer::FrameManager]
5
+ # @param parent_frame [Puppeteer::Frame|nil]
6
+ # @param frame_id [String]
7
+ # @param client [Puppeteer::CDPSession]
8
+ def initialize(frame_manager, parent_frame, frame_id, client)
9
9
  @frame_manager = frame_manager
10
- @client = client
11
10
  @parent_frame = parent_frame
12
11
  @id = frame_id
13
12
  @detached = false
14
13
 
15
14
  @loader_id = ''
16
15
  @lifecycle_events = Set.new
17
- @main_world = Puppeteer::DOMWorld.new(frame_manager, self, frame_manager.timeout_settings)
18
- @secondary_world = Puppeteer::DOMWorld.new(frame_manager, self, frame_manager.timeout_settings)
19
16
  @child_frames = Set.new
20
17
  if parent_frame
21
18
  parent_frame._child_frames << self
22
19
  end
20
+
21
+ update_client(client)
22
+ end
23
+
24
+ def inspect
25
+ values = %i[id parent_frame detached loader_id lifecycle_events child_frames].map do |sym|
26
+ value = instance_variable_get(:"@#{sym}")
27
+ "@#{sym}=#{value}"
28
+ end
29
+ "#<Puppeteer::Frame #{values.join(' ')}>"
30
+ end
31
+
32
+ def _client
33
+ @client
34
+ end
35
+
36
+ # @param client [Puppeteer::CDPSession]
37
+ private def update_client(client)
38
+ @client = client
39
+ @main_world = Puppeteer::DOMWorld.new(@client, @frame_manager, self, @frame_manager.timeout_settings)
40
+ @secondary_world = Puppeteer::DOMWorld.new(@client, @frame_manager, self, @frame_manager.timeout_settings)
41
+ end
42
+
43
+ def oop_frame?
44
+ @client != @frame_manager.client
23
45
  end
24
46
 
25
47
  attr_accessor :frame_manager, :id, :loader_id, :lifecycle_events, :main_world, :secondary_world
@@ -27,50 +27,67 @@ class Puppeteer::FrameManager
27
27
  # @type {!Set<string>}
28
28
  @isolated_worlds = Set.new
29
29
 
30
- @client.on_event('Page.frameAttached') do |event|
31
- handle_frame_attached(event['frameId'], event['parentFrameId'])
30
+ setup_listeners(@client)
31
+ end
32
+
33
+ private def setup_listeners(client)
34
+ client.on_event('Page.frameAttached') do |event|
35
+ handle_frame_attached(client, event['frameId'], event['parentFrameId'])
32
36
  end
33
- @client.on_event('Page.frameNavigated') do |event|
37
+ client.on_event('Page.frameNavigated') do |event|
34
38
  handle_frame_navigated(event['frame'])
35
39
  end
36
- @client.on_event('Page.navigatedWithinDocument') do |event|
40
+ client.on_event('Page.navigatedWithinDocument') do |event|
37
41
  handle_frame_navigated_within_document(event['frameId'], event['url'])
38
42
  end
39
- @client.on_event('Page.frameDetached') do |event|
40
- handle_frame_detached(event['frameId'])
43
+ client.on_event('Page.frameDetached') do |event|
44
+ handle_frame_detached(event['frameId'], event['reason'])
41
45
  end
42
- @client.on_event('Page.frameStoppedLoading') do |event|
46
+ client.on_event('Page.frameStoppedLoading') do |event|
43
47
  handle_frame_stopped_loading(event['frameId'])
44
48
  end
45
- @client.on_event('Runtime.executionContextCreated') do |event|
46
- handle_execution_context_created(event['context'])
49
+ client.on_event('Runtime.executionContextCreated') do |event|
50
+ handle_execution_context_created(event['context'], client)
47
51
  end
48
- @client.on_event('Runtime.executionContextDestroyed') do |event|
49
- handle_execution_context_destroyed(event['executionContextId'])
52
+ client.on_event('Runtime.executionContextDestroyed') do |event|
53
+ handle_execution_context_destroyed(event['executionContextId'], client)
50
54
  end
51
- @client.on_event('Runtime.executionContextsCleared') do |event|
52
- handle_execution_contexts_cleared
55
+ client.on_event('Runtime.executionContextsCleared') do |event|
56
+ handle_execution_contexts_cleared(client)
53
57
  end
54
- @client.on_event('Page.lifecycleEvent') do |event|
58
+ client.on_event('Page.lifecycleEvent') do |event|
55
59
  handle_lifecycle_event(event)
56
60
  end
61
+ client.on_event('Target.attachedToTarget') do |event|
62
+ handle_attached_to_target(event)
63
+ end
64
+ client.on_event('Target.detachedFromTarget') do |event|
65
+ handle_detached_from_target(event)
66
+ end
57
67
  end
58
68
 
59
69
  attr_reader :client, :timeout_settings
60
70
 
61
- private def init
71
+ private def init(cdp_session = nil)
72
+ client = cdp_session || @client
73
+
62
74
  results = await_all(
63
- @client.async_send_message('Page.enable'),
64
- @client.async_send_message('Page.getFrameTree'),
75
+ client.async_send_message('Page.enable'),
76
+ client.async_send_message('Page.getFrameTree'),
65
77
  )
66
78
  frame_tree = results.last['frameTree']
67
- handle_frame_tree(frame_tree)
79
+ handle_frame_tree(client, frame_tree)
68
80
  await_all(
69
- @client.async_send_message('Page.setLifecycleEventsEnabled', enabled: true),
70
- @client.async_send_message('Runtime.enable'),
81
+ client.async_send_message('Page.setLifecycleEventsEnabled', enabled: true),
82
+ client.async_send_message('Runtime.enable'),
71
83
  )
72
- ensure_isolated_world(UTILITY_WORLD_NAME)
73
- @network_manager.init
84
+ ensure_isolated_world(client, UTILITY_WORLD_NAME)
85
+ @network_manager.init unless cdp_session
86
+ rescue => err
87
+ # The target might have been closed before the initialization finished.
88
+ return if err.message.include?('Target closed') || err.message.include?('Session closed')
89
+
90
+ raise
74
91
  end
75
92
 
76
93
  define_async_method :async_init
@@ -154,6 +171,28 @@ class Puppeteer::FrameManager
154
171
  watcher.navigation_response
155
172
  end
156
173
 
174
+ # @param event [Hash]
175
+ def handle_attached_to_target(event)
176
+ return if event['targetInfo']['type'] != 'iframe'
177
+
178
+ frame = @frames[event['targetInfo']['targetId']]
179
+ session = Puppeteer::Connection.from_session(@client).session(event['sessionId'])
180
+
181
+ frame.send(:update_client, session)
182
+ setup_listeners(session)
183
+ async_init(session)
184
+ end
185
+
186
+ # @param event [Hash]
187
+ def handle_detached_from_target(event)
188
+ frame = @frames[event['targetId']]
189
+ if frame && frame.oop_frame?
190
+ # When an OOP iframe is removed from the page, it
191
+ # will only get a Target.detachedFromTarget event.
192
+ remove_frame_recursively(frame)
193
+ end
194
+ end
195
+
157
196
  # @param event [Hash]
158
197
  def handle_lifecycle_event(event)
159
198
  frame = @frames[event['frameId']]
@@ -170,16 +209,17 @@ class Puppeteer::FrameManager
170
209
  emit_event(FrameManagerEmittedEvents::LifecycleEvent, frame)
171
210
  end
172
211
 
212
+ # @param session [Puppeteer::CDPSession]
173
213
  # @param frame_tree [Hash]
174
- def handle_frame_tree(frame_tree)
214
+ def handle_frame_tree(session, frame_tree)
175
215
  if frame_tree['frame']['parentId']
176
- handle_frame_attached(frame_tree['frame']['id'], frame_tree['frame']['parentId'])
216
+ handle_frame_attached(session, frame_tree['frame']['id'], frame_tree['frame']['parentId'])
177
217
  end
178
218
  handle_frame_navigated(frame_tree['frame'])
179
219
  return if !frame_tree['childFrames']
180
220
 
181
221
  frame_tree['childFrames'].each do |child|
182
- handle_frame_tree(child)
222
+ handle_frame_tree(session, child)
183
223
  end
184
224
  end
185
225
 
@@ -204,15 +244,25 @@ class Puppeteer::FrameManager
204
244
  @frames[frame_id]
205
245
  end
206
246
 
207
- # @param {string} frameId
208
- # @param {?string} parentFrameId
209
- def handle_frame_attached(frame_id, parent_frame_id)
210
- return if @frames.has_key?(frame_id)
247
+ # @param session [Puppeteer::CDPSession]
248
+ # @param frameId [String]
249
+ # @param parentFrameId [String|nil]
250
+ def handle_frame_attached(session, frame_id, parent_frame_id)
251
+ if @frames.has_key?(frame_id)
252
+ frame = @frames[frame_id]
253
+ if session && frame.oop_frame?
254
+ # If an OOP iframes becomes a normal iframe again
255
+ # it is first attached to the parent page before
256
+ # the target is removed.
257
+ frame.send(:update_client, session)
258
+ end
259
+ return
260
+ end
211
261
  if !parent_frame_id
212
262
  raise ArgymentError.new('parent_frame_id must not be nil')
213
263
  end
214
264
  parent_frame = @frames[parent_frame_id]
215
- frame = Puppeteer::Frame.new(self, @client, parent_frame, frame_id)
265
+ frame = Puppeteer::Frame.new(self, parent_frame, frame_id, session)
216
266
  @frames[frame_id] = frame
217
267
 
218
268
  emit_event(FrameManagerEmittedEvents::FrameAttached, frame)
@@ -247,7 +297,7 @@ class Puppeteer::FrameManager
247
297
  frame.id = frame_payload['id']
248
298
  else
249
299
  # Initial main frame navigation.
250
- frame = Puppeteer::Frame.new(self, @client, nil, frame_payload['id'])
300
+ frame = Puppeteer::Frame.new(self, nil, frame_payload['id'], @client)
251
301
  end
252
302
  @frames[frame_payload['id']] = frame
253
303
  @main_frame = frame
@@ -259,22 +309,26 @@ class Puppeteer::FrameManager
259
309
  emit_event(FrameManagerEmittedEvents::FrameNavigated, frame)
260
310
  end
261
311
 
312
+ # @param session [Puppeteer::CDPSession]
262
313
  # @param name [String]
263
- def ensure_isolated_world(name)
264
- return if @isolated_worlds.include?(name)
265
- @isolated_worlds << name
314
+ private def ensure_isolated_world(session, name)
315
+ key = "#{session.id}:#{name}"
316
+ return if @isolated_worlds.include?(key)
317
+ @isolated_worlds << key
266
318
 
267
- @client.send_message('Page.addScriptToEvaluateOnNewDocument',
319
+ session.send_message('Page.addScriptToEvaluateOnNewDocument',
268
320
  source: "//# sourceURL=#{Puppeteer::ExecutionContext::EVALUATION_SCRIPT_URL}",
269
321
  worldName: name,
270
322
  )
271
- create_isolated_worlds_promises = frames.map do |frame|
272
- @client.async_send_message('Page.createIsolatedWorld',
273
- frameId: frame.id,
274
- grantUniveralAccess: true,
275
- worldName: name,
276
- )
277
- end
323
+ create_isolated_worlds_promises = frames.
324
+ select { |frame| frame._client == session }.
325
+ map do |frame|
326
+ session.async_send_message('Page.createIsolatedWorld',
327
+ frameId: frame.id,
328
+ grantUniveralAccess: true,
329
+ worldName: name,
330
+ )
331
+ end
278
332
  await_all(*create_isolated_worlds_promises)
279
333
  end
280
334
 
@@ -289,19 +343,31 @@ class Puppeteer::FrameManager
289
343
  end
290
344
 
291
345
  # @param frame_id [String]
292
- def handle_frame_detached(frame_id)
346
+ # @param reason [String]
347
+ def handle_frame_detached(frame_id, reason)
293
348
  frame = @frames[frame_id]
294
- if frame
295
- remove_frame_recursively(frame)
349
+ if reason == 'remove'
350
+ # Only remove the frame if the reason for the detached event is
351
+ # an actual removement of the frame.
352
+ # For frames that become OOP iframes, the reason would be 'swap'.
353
+ if frame
354
+ remove_frame_recursively(frame)
355
+ end
296
356
  end
297
357
  end
298
358
 
299
359
  # @param context_payload [Hash]
300
- def handle_execution_context_created(context_payload)
360
+ # @pram session [Puppeteer::CDPSession]
361
+ def handle_execution_context_created(context_payload, session)
301
362
  frame = if_present(context_payload.dig('auxData', 'frameId')) { |frame_id| @frames[frame_id] }
302
363
 
303
364
  world = nil
304
365
  if frame
366
+ # commented out the original implementation for allowing us to use Frame#evaluate on OOP iframe.
367
+ #
368
+ # # Only care about execution contexts created for the current session.
369
+ # return if @client != session
370
+
305
371
  if context_payload.dig('auxData', 'isDefault')
306
372
  world = frame.main_world
307
373
  elsif context_payload['name'] == UTILITY_WORLD_NAME && !frame.secondary_world.has_context?
@@ -316,34 +382,45 @@ class Puppeteer::FrameManager
316
382
  @isolated_worlds << context_payload['name']
317
383
  end
318
384
 
319
- context = Puppeteer::ExecutionContext.new(@client, context_payload, world)
385
+ context = Puppeteer::ExecutionContext.new(frame._client || @client, context_payload, world)
320
386
  if world
321
387
  world.context = context
322
388
  end
323
- @context_id_to_context[context_payload['id']] = context
389
+ key = "#{session.id}:#{context_payload['id']}"
390
+ @context_id_to_context[key] = context
324
391
  end
325
392
 
326
- # @param {number} executionContextId
327
- def handle_execution_context_destroyed(execution_context_id)
328
- context = @context_id_to_context[execution_context_id]
393
+ # @param execution_context_id [Integer]
394
+ # @param session [Puppeteer::CDPSEssion]
395
+ def handle_execution_context_destroyed(execution_context_id, session)
396
+ key = "#{session.id}:#{execution_context_id}"
397
+ context = @context_id_to_context[key]
329
398
  return unless context
330
- @context_id_to_context.delete(execution_context_id)
399
+ @context_id_to_context.delete(key)
331
400
  if context.world
332
401
  context.world.delete_context(execution_context_id)
333
402
  end
334
403
  end
335
404
 
336
- def handle_execution_contexts_cleared
337
- @context_id_to_context.each do |execution_context_id, context|
338
- if context.world
339
- context.world.delete_context(execution_context_id)
405
+ # @param session [Puppeteer::CDPSession]
406
+ def handle_execution_contexts_cleared(session)
407
+ @context_id_to_context.select! do |execution_context_id, context|
408
+ # Make sure to only clear execution contexts that belong
409
+ # to the current session.
410
+ if context.client != session
411
+ true # keep
412
+ else
413
+ if context.world
414
+ context.world.delete_context(execution_context_id)
415
+ end
416
+ false # remove
340
417
  end
341
418
  end
342
- @context_id_to_context.clear
343
419
  end
344
420
 
345
- def execution_context_by_id(context_id)
346
- @context_id_to_context[context_id] or raise "INTERNAL ERROR: missing context with id = #{context_id}"
421
+ def execution_context_by_id(context_id, session)
422
+ key = "#{session.id}:#{context_id}"
423
+ @context_id_to_context[key] or raise "INTERNAL ERROR: missing context with id = #{context_id}"
347
424
  end
348
425
 
349
426
  # @param {!Frame} frame
@@ -72,7 +72,15 @@ class Puppeteer::HTTPRequest
72
72
  end
73
73
 
74
74
  attr_reader :internal
75
- attr_reader :url, :resource_type, :method, :post_data, :headers, :response, :frame
75
+ attr_reader :url, :resource_type, :method, :post_data, :headers, :response, :frame, :initiator
76
+
77
+ def inspect
78
+ values = %i[request_id method url].map do |sym|
79
+ value = instance_variable_get(:"@#{sym}")
80
+ "@#{sym}=#{value}"
81
+ end
82
+ "#<Puppeteer::HTTPRequest #{values.join(' ')}>"
83
+ end
76
84
 
77
85
  private def assert_interception_allowed
78
86
  unless @allow_interception
@@ -34,13 +34,13 @@ module Puppeteer::Launcher
34
34
  if @launch_options.pipe?
35
35
  chrome_arguments << '--remote-debugging-pipe'
36
36
  else
37
- chrome_arguments << '--remote-debugging-port=0'
37
+ chrome_arguments << "--remote-debugging-port=#{@chrome_arg_options.debugging_port}"
38
38
  end
39
39
  end
40
40
 
41
41
  temporary_user_data_dir = nil
42
42
  if chrome_arguments.none? { |arg| arg.start_with?('--user-data-dir') }
43
- temporary_user_data_dir = Dir.mktmpdir('puppeteer_dev_chrome_profile-')
43
+ temporary_user_data_dir = Dir.mktmpdir('puppeteer_dev_chrome_profile-', ENV['PUPPETEER_TMP_DIR'])
44
44
  chrome_arguments << "--user-data-dir=#{temporary_user_data_dir}"
45
45
  end
46
46
 
@@ -78,7 +78,10 @@ module Puppeteer::Launcher
78
78
  close_callback: -> { runner.close },
79
79
  )
80
80
 
81
- browser.wait_for_target(predicate: ->(target) { target.type == 'page' })
81
+ browser.wait_for_target(
82
+ predicate: ->(target) { target.type == 'page' },
83
+ timeout: @launch_options.timeout,
84
+ )
82
85
 
83
86
  browser
84
87
  rescue
@@ -34,9 +34,10 @@ module Puppeteer::Launcher
34
34
  if @headless.nil?
35
35
  @headless = !@devtools
36
36
  end
37
+ @debugging_port = options[:debugging_port] || 0
37
38
  end
38
39
 
39
- attr_reader :args, :user_data_dir
40
+ attr_reader :args, :user_data_dir, :debugging_port
40
41
 
41
42
  def headless?
42
43
  @headless
@@ -28,7 +28,7 @@ module Puppeteer::Launcher
28
28
  end
29
29
 
30
30
  if firefox_arguments.none? { |arg| arg.start_with?('--remote-debugging-') }
31
- firefox_arguments << '--remote-debugging-port=0'
31
+ firefox_arguments << "--remote-debugging-port=#{@chrome_arg_options.debugging_port}"
32
32
  end
33
33
 
34
34
  temporary_user_data_dir = nil
@@ -71,7 +71,10 @@ module Puppeteer::Launcher
71
71
  close_callback: -> { runner.close },
72
72
  )
73
73
 
74
- browser.wait_for_target(predicate: ->(target) { target.type == 'page' })
74
+ browser.wait_for_target(
75
+ predicate: ->(target) { target.type == 'page' },
76
+ timeout: @launch_options.timeout,
77
+ )
75
78
 
76
79
  browser
77
80
  rescue
@@ -228,7 +231,7 @@ module Puppeteer::Launcher
228
231
  end
229
232
 
230
233
  private def create_profile(extra_prefs = {})
231
- Dir.mktmpdir('puppeteer_dev_firefox_profile-').tap do |profile_path|
234
+ Dir.mktmpdir('puppeteer_dev_firefox_profile-', ENV['PUPPETEER_TMP_DIR']).tap do |profile_path|
232
235
  server = 'dummy.test'
233
236
  default_preferences = {
234
237
  # Make sure Shield doesn't hit the network.
@@ -34,7 +34,7 @@ class Puppeteer::Page
34
34
  @type ||= 'png'
35
35
 
36
36
  if options[:quality]
37
- unless @type == 'jpeg'
37
+ if @type != 'jpeg' && @type != 'webp'
38
38
  raise ArgumentError.new("options.quality is unsupported for the #{@type} screenshots")
39
39
  end
40
40
  unless options[:quality].is_a?(Numeric)
@@ -50,19 +50,26 @@ class Puppeteer::Page
50
50
  @workers = {}
51
51
  @user_drag_interception_enabled = false
52
52
 
53
- @client.on_event('Target.attachedToTarget') do |event|
54
- if event['targetInfo']['type'] != 'worker'
53
+ @client.add_event_listener('Target.attachedToTarget') do |event|
54
+ if event['targetInfo']['type'] != 'worker' && event['targetInfo']['type'] != 'iframe'
55
55
  # If we don't detach from service workers, they will never die.
56
+ # We still want to attach to workers for emitting events.
57
+ # We still want to attach to iframes so sessions may interact with them.
58
+ # We detach from all other types out of an abundance of caution.
59
+ # See https://source.chromium.org/chromium/chromium/src/+/main:content/browser/devtools/devtools_agent_host_impl.cc?ss=chromium&q=f:devtools%20-f:out%20%22::kTypePage%5B%5D%22
60
+ # for the complete list of available types.
56
61
  @client.async_send_message('Target.detachFromTarget', sessionId: event['sessionId'])
57
62
  next
58
63
  end
59
64
 
60
- session = Puppeteer::Connection.from_session(@client).session(event['sessionId']) # rubocop:disable Lint/UselessAssignment
61
- # const worker = new Worker(session, event.targetInfo.url, this._addConsoleMessage.bind(this), this._handleException.bind(this));
62
- # this._workers.set(event.sessionId, worker);
63
- # this.emit(PageEmittedEvents::WorkerCreated, worker);
65
+ if event['targetInfo']['type'] == 'worker'
66
+ session = Puppeteer::Connection.from_session(@client).session(event['sessionId']) # rubocop:disable Lint/UselessAssignment
67
+ # const worker = new Worker(session, event.targetInfo.url, this._addConsoleMessage.bind(this), this._handleException.bind(this));
68
+ # this._workers.set(event.sessionId, worker);
69
+ # this.emit(PageEmittedEvents::WorkerCreated, worker);
70
+ end
64
71
  end
65
- @client.on_event('Target.detachedFromTarget') do |event|
72
+ @client.add_event_listener('Target.detachedFromTarget') do |event|
66
73
  session_id = event['sessionId']
67
74
  worker = @workers[session_id]
68
75
  next unless worker
@@ -103,10 +110,10 @@ class Puppeteer::Page
103
110
  @client.on_event('Page.loadEventFired') do |event|
104
111
  emit_event(PageEmittedEvents::Load)
105
112
  end
106
- @client.on('Runtime.consoleAPICalled') do |event|
113
+ @client.add_event_listener('Runtime.consoleAPICalled') do |event|
107
114
  handle_console_api(event)
108
115
  end
109
- @client.on('Runtime.bindingCalled') do |event|
116
+ @client.add_event_listener('Runtime.bindingCalled') do |event|
110
117
  handle_binding_called(event)
111
118
  end
112
119
  @client.on_event('Page.javascriptDialogOpening') do |event|
@@ -518,7 +525,7 @@ class Puppeteer::Page
518
525
  return
519
526
  end
520
527
 
521
- context = @frame_manager.execution_context_by_id(event['executionContextId'])
528
+ context = @frame_manager.execution_context_by_id(event['executionContextId'], @client)
522
529
  values = event['args'].map do |arg|
523
530
  remote_object = Puppeteer::RemoteObject.new(arg)
524
531
  Puppeteer::JSHandle.create(context: context, remote_object: remote_object)
@@ -664,19 +671,38 @@ class Puppeteer::Page
664
671
  private def wait_for_network_manager_event(event_name, predicate:, timeout:)
665
672
  option_timeout = timeout || @timeout_settings.timeout
666
673
 
667
- @wait_for_network_manager_event_listener_ids ||= {}
668
- if_present(@wait_for_network_manager_event_listener_ids[event_name]) do |listener_id|
674
+ promise = resolvable_future
675
+
676
+ listener_id = @frame_manager.network_manager.add_event_listener(event_name) do |event_target|
677
+ if predicate.call(event_target)
678
+ promise.fulfill(event_target)
679
+ end
680
+ end
681
+
682
+ begin
683
+ # Timeout.timeout(0) means "no limit" for timeout.
684
+ Timeout.timeout(option_timeout / 1000.0) do
685
+ await_any(promise, session_close_promise)
686
+ end
687
+ rescue Timeout::Error
688
+ raise Puppeteer::TimeoutError.new("waiting for #{event_name} failed: timeout #{option_timeout}ms exceeded")
689
+ ensure
669
690
  @frame_manager.network_manager.remove_event_listener(listener_id)
670
691
  end
692
+ end
693
+
694
+ private def wait_for_frame_manager_event(*event_names, predicate:, timeout:)
695
+ option_timeout = timeout || @timeout_settings.timeout
671
696
 
672
697
  promise = resolvable_future
673
698
 
674
- @wait_for_network_manager_event_listener_ids[event_name] =
675
- @frame_manager.network_manager.add_event_listener(event_name) do |event_target|
699
+ listener_ids = event_names.map do |event_name|
700
+ @frame_manager.add_event_listener(event_name) do |event_target|
676
701
  if predicate.call(event_target)
677
- promise.fulfill(event_target)
702
+ promise.fulfill(event_target) unless promise.resolved?
678
703
  end
679
704
  end
705
+ end
680
706
 
681
707
  begin
682
708
  # Timeout.timeout(0) means "no limit" for timeout.
@@ -684,9 +710,11 @@ class Puppeteer::Page
684
710
  await_any(promise, session_close_promise)
685
711
  end
686
712
  rescue Timeout::Error
687
- raise Puppeteer::TimeoutError.new("waiting for #{event_name} failed: timeout #{timeout}ms exceeded")
713
+ raise Puppeteer::TimeoutError.new("waiting for #{event_names.join(" or ")} failed: timeout #{option_timeout}ms exceeded")
688
714
  ensure
689
- @frame_manager.network_manager.remove_event_listener(@wait_for_network_manager_event_listener_ids[event_name])
715
+ listener_ids.each do |listener_id|
716
+ @frame_manager.remove_event_listener(listener_id)
717
+ end
690
718
  end
691
719
  end
692
720
 
@@ -709,7 +737,7 @@ class Puppeteer::Page
709
737
  if url
710
738
  -> (request) { request.url == url }
711
739
  else
712
- -> (request) { predicate.call(request) }
740
+ predicate
713
741
  end
714
742
 
715
743
  wait_for_network_manager_event(NetworkManagerEmittedEvents::Request,
@@ -743,7 +771,7 @@ class Puppeteer::Page
743
771
  if url
744
772
  -> (response) { response.url == url }
745
773
  else
746
- -> (response) { predicate.call(response) }
774
+ predicate
747
775
  end
748
776
 
749
777
  wait_for_network_manager_event(NetworkManagerEmittedEvents::Response,
@@ -758,6 +786,34 @@ class Puppeteer::Page
758
786
  # @param predicate [Proc(Puppeteer::HTTPRequest -> Boolean)]
759
787
  define_async_method :async_wait_for_response
760
788
 
789
+ def wait_for_frame(url: nil, predicate: nil, timeout: nil)
790
+ if !url && !predicate
791
+ raise ArgumentError.new('url or predicate must be specified')
792
+ end
793
+ if predicate && !predicate.is_a?(Proc)
794
+ raise ArgumentError.new('predicate must be a proc.')
795
+ end
796
+ frame_predicate =
797
+ if url
798
+ -> (frame) { frame.url == url }
799
+ else
800
+ predicate
801
+ end
802
+
803
+ wait_for_frame_manager_event(
804
+ FrameManagerEmittedEvents::FrameAttached,
805
+ FrameManagerEmittedEvents::FrameNavigated,
806
+ predicate: frame_predicate,
807
+ timeout: timeout,
808
+ )
809
+ end
810
+
811
+ # @!method async_wait_for_frame(url: nil, predicate: nil, timeout: nil)
812
+ #
813
+ # @param url [String]
814
+ # @param predicate [Proc(Puppeteer::Frame -> Boolean)]
815
+ define_async_method :async_wait_for_frame
816
+
761
817
  # @param timeout [number|nil]
762
818
  # @param wait_until [string|nil] 'load' | 'domcontentloaded' | 'networkidle0' | 'networkidle2'
763
819
  def go_back(timeout: nil, wait_until: nil)
@@ -44,6 +44,7 @@ class Puppeteer::Puppeteer
44
44
  args: nil,
45
45
  user_data_dir: nil,
46
46
  devtools: nil,
47
+ debugging_port: nil,
47
48
  headless: nil,
48
49
  ignore_https_errors: nil,
49
50
  default_viewport: NoViewport.new,
@@ -63,6 +64,7 @@ class Puppeteer::Puppeteer
63
64
  args: args,
64
65
  user_data_dir: user_data_dir,
65
66
  devtools: devtools,
67
+ debugging_port: debugging_port,
66
68
  headless: headless,
67
69
  ignore_https_errors: ignore_https_errors,
68
70
  default_viewport: default_viewport,
@@ -119,6 +119,11 @@ class Puppeteer::Target
119
119
  end
120
120
  end
121
121
 
122
+ # @internal
123
+ def raw_type
124
+ @target_info.type
125
+ end
126
+
122
127
  # @return {!Puppeteer.Browser}
123
128
  def browser
124
129
  @browser_context.browser
@@ -1,3 +1,3 @@
1
1
  module Puppeteer
2
- VERSION = '0.37.4'
2
+ VERSION = '0.38.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puppeteer-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.37.4
4
+ version: 0.38.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - YusukeIwaki
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-12-05 00:00:00.000000000 Z
11
+ date: 2021-12-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby