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 +4 -4
- data/CHANGELOG.md +13 -1
- data/docs/api_coverage.md +5 -5
- data/lib/puppeteer/cdp_session.rb +5 -0
- data/lib/puppeteer/dom_world.rb +9 -5
- data/lib/puppeteer/frame.rb +30 -8
- data/lib/puppeteer/frame_manager.rb +136 -59
- data/lib/puppeteer/http_request.rb +9 -1
- data/lib/puppeteer/launcher/chrome.rb +6 -3
- data/lib/puppeteer/launcher/chrome_arg_options.rb +2 -1
- data/lib/puppeteer/launcher/firefox.rb +6 -3
- data/lib/puppeteer/page/screenshot_options.rb +1 -1
- data/lib/puppeteer/page.rb +75 -19
- data/lib/puppeteer/puppeteer.rb +2 -0
- data/lib/puppeteer/target.rb +5 -0
- data/lib/puppeteer/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d18f0aae5e331fee42f66c8140bdc3cced9c0514a03ab65460c4036a94bc6eb2
|
4
|
+
data.tar.gz: babeb261ac3661a6738eb3d02dddeef22685590791fbd579483edd2da7800606
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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.
|
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
|
-
*
|
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
|
-
*
|
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
|
-
*
|
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
|
-
*
|
363
|
+
* id
|
364
364
|
* send
|
365
365
|
|
366
366
|
## Coverage
|
data/lib/puppeteer/dom_world.rb
CHANGED
@@ -45,10 +45,14 @@ class Puppeteer::DOMWorld
|
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
|
-
# @param
|
49
|
-
# @param
|
50
|
-
# @param
|
51
|
-
|
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
|
-
|
65
|
+
@client.on_event('Runtime.bindingCalled', &method(:handle_binding_called))
|
62
66
|
end
|
63
67
|
|
64
68
|
attr_reader :frame
|
data/lib/puppeteer/frame.rb
CHANGED
@@ -1,25 +1,47 @@
|
|
1
1
|
class Puppeteer::Frame
|
2
2
|
using Puppeteer::DefineAsyncMethod
|
3
3
|
|
4
|
-
# @param
|
5
|
-
# @param
|
6
|
-
# @param
|
7
|
-
# @param
|
8
|
-
def initialize(frame_manager,
|
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
|
31
|
-
|
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
|
-
|
37
|
+
client.on_event('Page.frameNavigated') do |event|
|
34
38
|
handle_frame_navigated(event['frame'])
|
35
39
|
end
|
36
|
-
|
40
|
+
client.on_event('Page.navigatedWithinDocument') do |event|
|
37
41
|
handle_frame_navigated_within_document(event['frameId'], event['url'])
|
38
42
|
end
|
39
|
-
|
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
|
-
|
46
|
+
client.on_event('Page.frameStoppedLoading') do |event|
|
43
47
|
handle_frame_stopped_loading(event['frameId'])
|
44
48
|
end
|
45
|
-
|
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
|
-
|
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
|
-
|
52
|
-
handle_execution_contexts_cleared
|
55
|
+
client.on_event('Runtime.executionContextsCleared') do |event|
|
56
|
+
handle_execution_contexts_cleared(client)
|
53
57
|
end
|
54
|
-
|
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
|
-
|
64
|
-
|
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
|
-
|
70
|
-
|
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
|
208
|
-
# @param
|
209
|
-
|
210
|
-
|
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,
|
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,
|
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
|
-
|
265
|
-
@isolated_worlds
|
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
|
-
|
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.
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
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
|
-
|
346
|
+
# @param reason [String]
|
347
|
+
def handle_frame_detached(frame_id, reason)
|
293
348
|
frame = @frames[frame_id]
|
294
|
-
if
|
295
|
-
|
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
|
-
|
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
|
-
|
389
|
+
key = "#{session.id}:#{context_payload['id']}"
|
390
|
+
@context_id_to_context[key] = context
|
324
391
|
end
|
325
392
|
|
326
|
-
# @param
|
327
|
-
|
328
|
-
|
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(
|
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
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
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
|
-
|
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 <<
|
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(
|
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 <<
|
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(
|
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
|
-
|
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)
|
data/lib/puppeteer/page.rb
CHANGED
@@ -50,19 +50,26 @@ class Puppeteer::Page
|
|
50
50
|
@workers = {}
|
51
51
|
@user_drag_interception_enabled = false
|
52
52
|
|
53
|
-
@client.
|
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
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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.
|
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.
|
113
|
+
@client.add_event_listener('Runtime.consoleAPICalled') do |event|
|
107
114
|
handle_console_api(event)
|
108
115
|
end
|
109
|
-
@client.
|
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
|
-
|
668
|
-
|
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
|
-
|
675
|
-
@frame_manager.
|
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 #{
|
713
|
+
raise Puppeteer::TimeoutError.new("waiting for #{event_names.join(" or ")} failed: timeout #{option_timeout}ms exceeded")
|
688
714
|
ensure
|
689
|
-
|
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
|
-
|
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
|
-
|
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)
|
data/lib/puppeteer/puppeteer.rb
CHANGED
@@ -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,
|
data/lib/puppeteer/target.rb
CHANGED
data/lib/puppeteer/version.rb
CHANGED
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.
|
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-
|
11
|
+
date: 2021-12-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|