puppeteer-ruby 0.45.6 → 0.50.0.alpha5
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/.rubocop.yml +1 -3
- data/AGENTS.md +169 -0
- data/CLAUDE/README.md +41 -0
- data/CLAUDE/architecture.md +253 -0
- data/CLAUDE/cdp_protocol.md +230 -0
- data/CLAUDE/concurrency.md +216 -0
- data/CLAUDE/porting_puppeteer.md +575 -0
- data/CLAUDE/rbs_type_checking.md +101 -0
- data/CLAUDE/spec_migration_plans.md +1041 -0
- data/CLAUDE/testing.md +278 -0
- data/CLAUDE.md +242 -0
- data/README.md +8 -0
- data/Rakefile +7 -0
- data/Steepfile +28 -0
- data/docs/api_coverage.md +105 -56
- data/lib/puppeteer/aria_query_handler.rb +3 -2
- data/lib/puppeteer/async_utils.rb +214 -0
- data/lib/puppeteer/browser.rb +98 -56
- data/lib/puppeteer/browser_connector.rb +18 -3
- data/lib/puppeteer/browser_context.rb +196 -3
- data/lib/puppeteer/browser_runner.rb +18 -10
- data/lib/puppeteer/cdp_session.rb +67 -23
- data/lib/puppeteer/chrome_target_manager.rb +65 -40
- data/lib/puppeteer/connection.rb +55 -36
- data/lib/puppeteer/console_message.rb +9 -1
- data/lib/puppeteer/console_patch.rb +47 -0
- data/lib/puppeteer/css_coverage.rb +5 -3
- data/lib/puppeteer/custom_query_handler.rb +80 -33
- data/lib/puppeteer/define_async_method.rb +31 -37
- data/lib/puppeteer/dialog.rb +47 -14
- data/lib/puppeteer/element_handle.rb +231 -62
- data/lib/puppeteer/emulation_manager.rb +1 -1
- data/lib/puppeteer/env.rb +1 -1
- data/lib/puppeteer/errors.rb +25 -2
- data/lib/puppeteer/event_callbackable.rb +15 -0
- data/lib/puppeteer/events.rb +4 -0
- data/lib/puppeteer/execution_context.rb +148 -3
- data/lib/puppeteer/file_chooser.rb +6 -0
- data/lib/puppeteer/frame.rb +162 -91
- data/lib/puppeteer/frame_manager.rb +69 -48
- data/lib/puppeteer/http_request.rb +114 -38
- data/lib/puppeteer/http_response.rb +24 -7
- data/lib/puppeteer/isolated_world.rb +64 -41
- data/lib/puppeteer/js_coverage.rb +5 -3
- data/lib/puppeteer/js_handle.rb +58 -16
- data/lib/puppeteer/keyboard.rb +30 -17
- data/lib/puppeteer/launcher/browser_options.rb +3 -1
- data/lib/puppeteer/launcher/chrome.rb +8 -5
- data/lib/puppeteer/launcher/launch_options.rb +7 -2
- data/lib/puppeteer/launcher.rb +4 -8
- data/lib/puppeteer/lifecycle_watcher.rb +38 -22
- data/lib/puppeteer/mouse.rb +273 -64
- data/lib/puppeteer/network_event_manager.rb +7 -0
- data/lib/puppeteer/network_manager.rb +393 -112
- data/lib/puppeteer/page/screenshot_task_queue.rb +14 -4
- data/lib/puppeteer/page.rb +568 -226
- data/lib/puppeteer/puppeteer.rb +171 -64
- data/lib/puppeteer/query_handler_manager.rb +112 -16
- data/lib/puppeteer/reactor_runner.rb +247 -0
- data/lib/puppeteer/remote_object.rb +127 -47
- data/lib/puppeteer/target.rb +74 -27
- data/lib/puppeteer/task_manager.rb +3 -1
- data/lib/puppeteer/timeout_helper.rb +6 -10
- data/lib/puppeteer/touch_handle.rb +39 -0
- data/lib/puppeteer/touch_screen.rb +72 -22
- data/lib/puppeteer/tracing.rb +3 -3
- data/lib/puppeteer/version.rb +1 -1
- data/lib/puppeteer/wait_task.rb +264 -101
- data/lib/puppeteer/web_socket.rb +2 -2
- data/lib/puppeteer/web_socket_transport.rb +91 -27
- data/lib/puppeteer/web_worker.rb +175 -0
- data/lib/puppeteer.rb +20 -4
- data/puppeteer-ruby.gemspec +15 -11
- data/sig/_external.rbs +8 -0
- data/sig/_supplementary.rbs +314 -0
- data/sig/puppeteer/browser.rbs +166 -0
- data/sig/puppeteer/cdp_session.rbs +64 -0
- data/sig/puppeteer/dialog.rbs +41 -0
- data/sig/puppeteer/element_handle.rbs +305 -0
- data/sig/puppeteer/execution_context.rbs +87 -0
- data/sig/puppeteer/frame.rbs +226 -0
- data/sig/puppeteer/http_request.rbs +214 -0
- data/sig/puppeteer/http_response.rbs +89 -0
- data/sig/puppeteer/js_handle.rbs +64 -0
- data/sig/puppeteer/keyboard.rbs +40 -0
- data/sig/puppeteer/mouse.rbs +113 -0
- data/sig/puppeteer/page.rbs +515 -0
- data/sig/puppeteer/puppeteer.rbs +98 -0
- data/sig/puppeteer/remote_object.rbs +78 -0
- data/sig/puppeteer/touch_handle.rbs +21 -0
- data/sig/puppeteer/touch_screen.rbs +35 -0
- data/sig/puppeteer/web_worker.rbs +83 -0
- metadata +116 -45
- data/CHANGELOG.md +0 -397
- data/lib/puppeteer/concurrent_ruby_utils.rb +0 -81
- data/lib/puppeteer/firefox_target_manager.rb +0 -157
- data/lib/puppeteer/launcher/firefox.rb +0 -453
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'async/semaphore'
|
|
2
|
+
|
|
1
3
|
class Puppeteer::NetworkManager
|
|
2
4
|
include Puppeteer::DebugPrint
|
|
3
5
|
include Puppeteer::EventCallbackable
|
|
@@ -16,8 +18,8 @@ class Puppeteer::NetworkManager
|
|
|
16
18
|
class InternalNetworkCondition
|
|
17
19
|
attr_writer :offline, :upload, :download, :latency
|
|
18
20
|
|
|
19
|
-
def initialize(
|
|
20
|
-
@
|
|
21
|
+
def initialize(sender)
|
|
22
|
+
@sender = sender
|
|
21
23
|
@offline = false
|
|
22
24
|
@upload = -1
|
|
23
25
|
@download = -1
|
|
@@ -43,13 +45,21 @@ class Puppeteer::NetworkManager
|
|
|
43
45
|
update_network_conditions
|
|
44
46
|
end
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
def params
|
|
49
|
+
{
|
|
48
50
|
offline: @offline,
|
|
49
51
|
latency: @latency,
|
|
50
52
|
downloadThroughput: @download,
|
|
51
53
|
uploadThroughput: @upload,
|
|
52
|
-
|
|
54
|
+
}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def refresh
|
|
58
|
+
update_network_conditions
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private def update_network_conditions
|
|
62
|
+
@sender.call('Network.emulateNetworkConditions', params)
|
|
53
63
|
end
|
|
54
64
|
end
|
|
55
65
|
|
|
@@ -72,50 +82,51 @@ class Puppeteer::NetworkManager
|
|
|
72
82
|
# @param {!Puppeteer.CDPSession} client
|
|
73
83
|
# @param {boolean} ignoreHTTPSErrors
|
|
74
84
|
# @param {!Puppeteer.FrameManager} frameManager
|
|
75
|
-
|
|
85
|
+
# @param {boolean} network_enabled
|
|
86
|
+
def initialize(client, ignore_https_errors, frame_manager, network_enabled: true)
|
|
76
87
|
@client = client
|
|
77
88
|
@ignore_https_errors = ignore_https_errors
|
|
78
89
|
@frame_manager = frame_manager
|
|
90
|
+
@network_enabled = network_enabled
|
|
79
91
|
@network_event_manager = Puppeteer::NetworkEventManager.new
|
|
92
|
+
@clients = Set.new
|
|
93
|
+
@initialized = false
|
|
80
94
|
|
|
81
95
|
@extra_http_headers = {}
|
|
96
|
+
@user_agent = nil
|
|
97
|
+
@user_agent_metadata = nil
|
|
82
98
|
|
|
83
99
|
@attempted_authentications = Set.new
|
|
84
100
|
@user_request_interception_enabled = false
|
|
85
101
|
@protocol_request_interception_enabled = false
|
|
86
|
-
@user_cache_disabled =
|
|
87
|
-
@internal_network_condition = InternalNetworkCondition.new(
|
|
102
|
+
@user_cache_disabled = nil
|
|
103
|
+
@internal_network_condition = InternalNetworkCondition.new(method(:send_to_clients))
|
|
104
|
+
@interception_semaphore = Async::Semaphore.new(1)
|
|
88
105
|
|
|
89
|
-
@client
|
|
90
|
-
handle_request_paused(event)
|
|
91
|
-
end
|
|
92
|
-
@client.on_event('Fetch.authRequired') do |event|
|
|
93
|
-
handle_auth_required(event)
|
|
94
|
-
end
|
|
95
|
-
@client.on_event('Network.requestWillBeSent') do |event|
|
|
96
|
-
handle_request_will_be_sent(event)
|
|
97
|
-
end
|
|
98
|
-
@client.on_event('Network.requestServedFromCache') do |event|
|
|
99
|
-
handle_request_served_from_cache(event)
|
|
100
|
-
end
|
|
101
|
-
@client.on_event('Network.responseReceived') do |event|
|
|
102
|
-
handle_response_received(event)
|
|
103
|
-
end
|
|
104
|
-
@client.on_event('Network.loadingFinished') do |event|
|
|
105
|
-
handle_loading_finished(event)
|
|
106
|
-
end
|
|
107
|
-
@client.on_event('Network.loadingFailed') do |event|
|
|
108
|
-
handle_loading_failed(event)
|
|
109
|
-
end
|
|
110
|
-
@client.on_event('Network.responseReceivedExtraInfo') do |event|
|
|
111
|
-
handle_response_received_extra_info(event)
|
|
112
|
-
end
|
|
106
|
+
add_client(@client)
|
|
113
107
|
end
|
|
114
108
|
|
|
115
109
|
def init
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
110
|
+
apply_to_clients do |client|
|
|
111
|
+
configure_client(client)
|
|
112
|
+
end
|
|
113
|
+
@initialized = true
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def add_client(client)
|
|
117
|
+
return unless @network_enabled
|
|
118
|
+
return if @clients.include?(client)
|
|
119
|
+
|
|
120
|
+
@clients << client
|
|
121
|
+
setup_listeners(client)
|
|
122
|
+
if @initialized
|
|
123
|
+
if Async::Task.current?
|
|
124
|
+
Async do
|
|
125
|
+
configure_client(client)
|
|
126
|
+
end
|
|
127
|
+
else
|
|
128
|
+
configure_client(client)
|
|
129
|
+
end
|
|
119
130
|
end
|
|
120
131
|
end
|
|
121
132
|
|
|
@@ -127,10 +138,87 @@ class Puppeteer::NetworkManager
|
|
|
127
138
|
"#<Puppeteer::HTTPRequest #{values.join(' ')}>"
|
|
128
139
|
end
|
|
129
140
|
|
|
141
|
+
private def apply_to_clients
|
|
142
|
+
@clients.each do |client|
|
|
143
|
+
yield client
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
private def ignore_client_error?(error)
|
|
148
|
+
message = error&.message
|
|
149
|
+
return false unless message
|
|
150
|
+
|
|
151
|
+
lowered = message.downcase
|
|
152
|
+
return true if lowered.include?('target closed')
|
|
153
|
+
return true if lowered.include?('session closed')
|
|
154
|
+
return true if lowered.include?('not supported')
|
|
155
|
+
return true if lowered.include?("wasn't found")
|
|
156
|
+
|
|
157
|
+
false
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
private def safe_send_message(client, method, params = {})
|
|
161
|
+
client.send_message(method, params)
|
|
162
|
+
rescue => err
|
|
163
|
+
raise unless ignore_client_error?(err)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
private def send_to_clients(method, params)
|
|
167
|
+
apply_to_clients do |client|
|
|
168
|
+
safe_send_message(client, method, params)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
private def apply_extra_http_headers(client)
|
|
173
|
+
safe_send_message(client, 'Network.setExtraHTTPHeaders', headers: @extra_http_headers)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
private def apply_user_agent(client)
|
|
177
|
+
return unless @user_agent
|
|
178
|
+
|
|
179
|
+
safe_send_message(client, 'Network.setUserAgentOverride', {
|
|
180
|
+
userAgent: @user_agent,
|
|
181
|
+
userAgentMetadata: @user_agent_metadata,
|
|
182
|
+
}.compact)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
private def apply_protocol_cache_disabled(client)
|
|
186
|
+
return if @user_cache_disabled.nil?
|
|
187
|
+
|
|
188
|
+
safe_send_message(client, 'Network.setCacheDisabled', cacheDisabled: @user_cache_disabled)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
private def apply_protocol_request_interception(client)
|
|
192
|
+
if @protocol_request_interception_enabled
|
|
193
|
+
safe_send_message(client, 'Fetch.enable',
|
|
194
|
+
handleAuthRequests: true,
|
|
195
|
+
patterns: [{ urlPattern: '*' }],
|
|
196
|
+
)
|
|
197
|
+
else
|
|
198
|
+
safe_send_message(client, 'Fetch.disable')
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
private def configure_client(client)
|
|
203
|
+
safe_send_message(client, 'Network.enable')
|
|
204
|
+
if @ignore_https_errors
|
|
205
|
+
safe_send_message(client, 'Security.setIgnoreCertificateErrors', ignore: true)
|
|
206
|
+
end
|
|
207
|
+
apply_extra_http_headers(client)
|
|
208
|
+
apply_user_agent(client)
|
|
209
|
+
apply_protocol_cache_disabled(client)
|
|
210
|
+
apply_protocol_request_interception(client)
|
|
211
|
+
safe_send_message(client, 'Network.emulateNetworkConditions', @internal_network_condition.params)
|
|
212
|
+
end
|
|
213
|
+
|
|
130
214
|
# @param username [String|NilClass]
|
|
131
215
|
# @param password [String|NilClass]
|
|
132
216
|
def authenticate(username:, password:)
|
|
133
|
-
|
|
217
|
+
if username.nil? && password.nil?
|
|
218
|
+
@credentials = nil
|
|
219
|
+
else
|
|
220
|
+
@credentials = Credentials.new(username: username, password: password)
|
|
221
|
+
end
|
|
134
222
|
update_protocol_request_interception
|
|
135
223
|
end
|
|
136
224
|
|
|
@@ -139,12 +227,27 @@ class Puppeteer::NetworkManager
|
|
|
139
227
|
new_extra_http_headers = {}
|
|
140
228
|
headers.each do |key, value|
|
|
141
229
|
unless value.is_a?(String)
|
|
142
|
-
|
|
230
|
+
type_description =
|
|
231
|
+
case value
|
|
232
|
+
when Numeric
|
|
233
|
+
'number'
|
|
234
|
+
when TrueClass, FalseClass
|
|
235
|
+
'boolean'
|
|
236
|
+
when NilClass
|
|
237
|
+
'null'
|
|
238
|
+
when Symbol
|
|
239
|
+
'symbol'
|
|
240
|
+
when Array, Hash
|
|
241
|
+
'object'
|
|
242
|
+
else
|
|
243
|
+
value.class.to_s
|
|
244
|
+
end
|
|
245
|
+
raise ArgumentError.new("Expected value of header \"#{key}\" to be String, but \"#{type_description}\" is found.")
|
|
143
246
|
end
|
|
144
247
|
new_extra_http_headers[key.downcase] = value
|
|
145
248
|
end
|
|
146
249
|
@extra_http_headers = new_extra_http_headers
|
|
147
|
-
|
|
250
|
+
apply_to_clients { |client| apply_extra_http_headers(client) }
|
|
148
251
|
end
|
|
149
252
|
|
|
150
253
|
# @return {!Object<string, string>}
|
|
@@ -169,10 +272,9 @@ class Puppeteer::NetworkManager
|
|
|
169
272
|
# @param user_agent [String]
|
|
170
273
|
# @param user_agent_metadata [Hash]
|
|
171
274
|
def set_user_agent(user_agent, user_agent_metadata = nil)
|
|
172
|
-
@
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}.compact)
|
|
275
|
+
@user_agent = user_agent
|
|
276
|
+
@user_agent_metadata = user_agent_metadata
|
|
277
|
+
apply_to_clients { |client| apply_user_agent(client) }
|
|
176
278
|
end
|
|
177
279
|
alias_method :user_agent=, :set_user_agent
|
|
178
280
|
|
|
@@ -186,61 +288,106 @@ class Puppeteer::NetworkManager
|
|
|
186
288
|
update_protocol_request_interception
|
|
187
289
|
end
|
|
188
290
|
|
|
291
|
+
private def setup_listeners(client)
|
|
292
|
+
client.add_event_listener(CDPSessionEmittedEvents::Disconnected) do
|
|
293
|
+
@clients.delete(client)
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
client.on_event('Fetch.requestPaused') do |event|
|
|
297
|
+
handle_request_paused(event, client)
|
|
298
|
+
end
|
|
299
|
+
client.on_event('Fetch.authRequired') do |event|
|
|
300
|
+
handle_auth_required(event, client)
|
|
301
|
+
end
|
|
302
|
+
client.on_event('Network.requestWillBeSent') do |event|
|
|
303
|
+
handle_request_will_be_sent(event, client)
|
|
304
|
+
end
|
|
305
|
+
client.on_event('Network.requestWillBeSentExtraInfo') do |event|
|
|
306
|
+
handle_request_will_be_sent_extra_info(event, client)
|
|
307
|
+
end
|
|
308
|
+
client.on_event('Network.requestServedFromCache') do |event|
|
|
309
|
+
handle_request_served_from_cache(event, client)
|
|
310
|
+
end
|
|
311
|
+
client.on_event('Network.responseReceived') do |event|
|
|
312
|
+
handle_response_received(event, client)
|
|
313
|
+
end
|
|
314
|
+
client.on_event('Network.loadingFinished') do |event|
|
|
315
|
+
handle_loading_finished(event, client)
|
|
316
|
+
end
|
|
317
|
+
client.on_event('Network.loadingFailed') do |event|
|
|
318
|
+
handle_loading_failed(event, client)
|
|
319
|
+
end
|
|
320
|
+
client.on_event('Network.responseReceivedExtraInfo') do |event|
|
|
321
|
+
handle_response_received_extra_info(event, client)
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
189
325
|
private def update_protocol_request_interception
|
|
190
326
|
enabled = @user_request_interception_enabled || !@credentials.nil?
|
|
191
327
|
return if @protocol_request_interception_enabled == enabled
|
|
192
328
|
@protocol_request_interception_enabled = enabled
|
|
193
329
|
|
|
194
|
-
if
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
patterns: [{ urlPattern: '*' }],
|
|
199
|
-
)
|
|
200
|
-
else
|
|
201
|
-
update_protocol_cache_disabled
|
|
202
|
-
@client.async_send_message('Fetch.disable')
|
|
203
|
-
end
|
|
330
|
+
@user_cache_disabled = false if @user_cache_disabled.nil?
|
|
331
|
+
|
|
332
|
+
update_protocol_cache_disabled
|
|
333
|
+
apply_to_clients { |client| apply_protocol_request_interception(client) }
|
|
204
334
|
end
|
|
205
335
|
|
|
206
336
|
private def update_protocol_cache_disabled
|
|
207
|
-
|
|
208
|
-
|
|
337
|
+
return if @user_cache_disabled.nil?
|
|
338
|
+
|
|
339
|
+
apply_to_clients { |client| apply_protocol_cache_disabled(client) }
|
|
209
340
|
end
|
|
210
341
|
|
|
211
|
-
private def handle_request_will_be_sent(event)
|
|
342
|
+
private def handle_request_will_be_sent(event, client)
|
|
343
|
+
network_request_id = event['requestId']
|
|
344
|
+
event_url = event.dig('request', 'url')
|
|
345
|
+
if event_url
|
|
346
|
+
url_fragment = event.dig('request', 'urlFragment')
|
|
347
|
+
event_url += url_fragment if url_fragment
|
|
348
|
+
end
|
|
349
|
+
existing_request = @network_event_manager.get_request(network_request_id)
|
|
350
|
+
if existing_request &&
|
|
351
|
+
existing_request.url == event_url &&
|
|
352
|
+
existing_request.method == event.dig('request', 'method')
|
|
353
|
+
if_present(@network_event_manager.request_extra_info(network_request_id).shift) do |extra_info|
|
|
354
|
+
existing_request.update_headers(extra_info['headers'])
|
|
355
|
+
end
|
|
356
|
+
return
|
|
357
|
+
end
|
|
358
|
+
|
|
212
359
|
# Request interception doesn't happen for data URLs with Network Service.
|
|
213
360
|
if @user_request_interception_enabled && !event['request']['url'].start_with?('data:')
|
|
214
|
-
network_request_id = event['requestId']
|
|
215
361
|
@network_event_manager.store_request_will_be_sent(network_request_id, event)
|
|
216
362
|
|
|
217
363
|
# CDP may have sent a Fetch.requestPaused event already. Check for it.
|
|
218
364
|
if_present(@network_event_manager.get_request_paused(network_request_id)) do |request_paused_event|
|
|
219
365
|
fetch_request_id = request_paused_event['requestId']
|
|
220
366
|
patch_request_event_headers(event, request_paused_event)
|
|
221
|
-
handle_request(event, fetch_request_id)
|
|
367
|
+
handle_request(event, fetch_request_id, client: client)
|
|
222
368
|
@network_event_manager.forget_request_paused(network_request_id)
|
|
223
369
|
end
|
|
224
370
|
|
|
225
371
|
return
|
|
226
372
|
end
|
|
227
|
-
handle_request(event, nil)
|
|
373
|
+
handle_request(event, nil, client: client)
|
|
228
374
|
end
|
|
229
375
|
|
|
230
|
-
private def handle_auth_required(event)
|
|
376
|
+
private def handle_auth_required(event, client)
|
|
377
|
+
auth_request_id = event['requestId']
|
|
231
378
|
response = 'Default'
|
|
232
|
-
if @attempted_authentications.include?(
|
|
379
|
+
if @attempted_authentications.include?(auth_request_id)
|
|
233
380
|
response = 'CancelAuth'
|
|
234
381
|
elsif @credentials
|
|
235
382
|
response = 'ProvideCredentials'
|
|
236
|
-
@attempted_authentications <<
|
|
383
|
+
@attempted_authentications << auth_request_id
|
|
237
384
|
end
|
|
238
385
|
|
|
239
386
|
username = @credentials&.username
|
|
240
387
|
password = @credentials&.password
|
|
241
388
|
|
|
242
389
|
begin
|
|
243
|
-
|
|
390
|
+
client.send_message('Fetch.continueWithAuth',
|
|
244
391
|
requestId: event['requestId'],
|
|
245
392
|
authChallengeResponse: {
|
|
246
393
|
response: response,
|
|
@@ -253,18 +400,22 @@ class Puppeteer::NetworkManager
|
|
|
253
400
|
end
|
|
254
401
|
end
|
|
255
402
|
|
|
256
|
-
private def handle_request_paused(event)
|
|
403
|
+
private def handle_request_paused(event, client)
|
|
257
404
|
if !@user_request_interception_enabled && @protocol_request_interception_enabled
|
|
258
405
|
begin
|
|
259
|
-
|
|
406
|
+
client.send_message('Fetch.continueRequest', requestId: event['requestId'])
|
|
260
407
|
rescue => err
|
|
261
408
|
debug_puts(err)
|
|
262
409
|
end
|
|
263
410
|
end
|
|
264
411
|
|
|
265
|
-
|
|
412
|
+
request_id = event['networkId']
|
|
266
413
|
fetch_request_id = event['requestId']
|
|
267
|
-
|
|
414
|
+
if !request_id || request_id.empty?
|
|
415
|
+
handle_request_without_network_instrumentation(event, client)
|
|
416
|
+
return
|
|
417
|
+
end
|
|
418
|
+
network_request_id = request_id
|
|
268
419
|
|
|
269
420
|
request_will_be_sent_event = @network_event_manager.get_request_will_be_sent(network_request_id)
|
|
270
421
|
|
|
@@ -279,9 +430,16 @@ class Puppeteer::NetworkManager
|
|
|
279
430
|
|
|
280
431
|
if request_will_be_sent_event
|
|
281
432
|
patch_request_event_headers(request_will_be_sent_event, event)
|
|
282
|
-
handle_request(request_will_be_sent_event, fetch_request_id)
|
|
433
|
+
handle_request(request_will_be_sent_event, fetch_request_id, client: client)
|
|
283
434
|
else
|
|
284
|
-
|
|
435
|
+
if event['redirectedRequestId']
|
|
436
|
+
handle_redirect_request_paused(event, network_request_id, fetch_request_id, client)
|
|
437
|
+
elsif event['resourceType'] && event['resourceType'].to_s.downcase != 'document'
|
|
438
|
+
request_event = build_request_event_from_paused(event, request_id)
|
|
439
|
+
handle_request_from_paused(request_event, fetch_request_id, [], client: client)
|
|
440
|
+
else
|
|
441
|
+
@network_event_manager.store_request_paused(network_request_id, event)
|
|
442
|
+
end
|
|
285
443
|
end
|
|
286
444
|
end
|
|
287
445
|
|
|
@@ -291,7 +449,90 @@ class Puppeteer::NetworkManager
|
|
|
291
449
|
request_paused_event['request']['headers'])
|
|
292
450
|
end
|
|
293
451
|
|
|
294
|
-
private def
|
|
452
|
+
private def handle_request_will_be_sent_extra_info(event, client)
|
|
453
|
+
network_request_id = event['requestId']
|
|
454
|
+
request = @network_event_manager.get_request(network_request_id)
|
|
455
|
+
if request
|
|
456
|
+
request.update_headers(event['headers'])
|
|
457
|
+
else
|
|
458
|
+
@network_event_manager.request_extra_info(network_request_id) << event
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
private def with_interception_lock
|
|
463
|
+
return yield unless Async::Task.current?
|
|
464
|
+
|
|
465
|
+
@interception_semaphore.acquire do
|
|
466
|
+
yield
|
|
467
|
+
end
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
private def handle_request_without_network_instrumentation(event, client)
|
|
471
|
+
frame = if_present(event['frameId']) { |frame_id| @frame_manager.frame(frame_id) }
|
|
472
|
+
request = Puppeteer::HTTPRequest.new(client, frame, event['requestId'], @user_request_interception_enabled, event, [])
|
|
473
|
+
emit_event(NetworkManagerEmittedEvents::Request, request)
|
|
474
|
+
begin
|
|
475
|
+
with_interception_lock { request.finalize_interceptions }
|
|
476
|
+
rescue => err
|
|
477
|
+
debug_puts(err)
|
|
478
|
+
end
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
private def handle_redirect_request_paused(event, network_request_id, fetch_request_id, client)
|
|
482
|
+
redirect_chain = []
|
|
483
|
+
if_present(@network_event_manager.get_request(network_request_id)) do |request|
|
|
484
|
+
response_payload = build_synthetic_redirect_response(event)
|
|
485
|
+
handle_request_redirect(request, response_payload, nil)
|
|
486
|
+
redirect_chain = request.internal.redirect_chain
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
request_event = build_request_event_from_paused(event, event['networkId'])
|
|
490
|
+
handle_request_from_paused(request_event, fetch_request_id, redirect_chain, client: client)
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
private def build_request_event_from_paused(event, request_id)
|
|
494
|
+
{
|
|
495
|
+
'requestId' => request_id,
|
|
496
|
+
'request' => event['request'],
|
|
497
|
+
'type' => event['resourceType'],
|
|
498
|
+
'frameId' => event['frameId'],
|
|
499
|
+
'initiator' => event['initiator'],
|
|
500
|
+
}.compact
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
private def build_synthetic_redirect_response(event)
|
|
504
|
+
status = event['responseStatusCode'] || 302
|
|
505
|
+
headers = {}
|
|
506
|
+
if_present(event.dig('request', 'url')) do |url|
|
|
507
|
+
headers['location'] = url
|
|
508
|
+
end
|
|
509
|
+
{
|
|
510
|
+
'status' => status,
|
|
511
|
+
'statusText' => Puppeteer::HTTPRequest::STATUS_TEXTS[status.to_s] || 'Found',
|
|
512
|
+
'headers' => headers,
|
|
513
|
+
'fromDiskCache' => false,
|
|
514
|
+
'fromServiceWorker' => false,
|
|
515
|
+
}
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
private def handle_request_from_paused(event, fetch_request_id, redirect_chain, client:)
|
|
519
|
+
network_request_id = event['requestId']
|
|
520
|
+
frame = if_present(event['frameId']) { |frame_id| @frame_manager.frame(frame_id) }
|
|
521
|
+
request = Puppeteer::HTTPRequest.new(client, frame, fetch_request_id, @user_request_interception_enabled, event, redirect_chain)
|
|
522
|
+
if_present(@network_event_manager.request_extra_info(network_request_id).shift) do |extra_info|
|
|
523
|
+
request.update_headers(extra_info['headers'])
|
|
524
|
+
end
|
|
525
|
+
@network_event_manager.store_request(network_request_id, request)
|
|
526
|
+
emit_event(NetworkManagerEmittedEvents::Request, request)
|
|
527
|
+
begin
|
|
528
|
+
with_interception_lock { request.finalize_interceptions }
|
|
529
|
+
rescue => err
|
|
530
|
+
debug_puts(err)
|
|
531
|
+
end
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
private def handle_request(event, fetch_request_id, from_memory_cache: false, client:)
|
|
535
|
+
network_request_id = event['requestId']
|
|
295
536
|
redirect_chain = []
|
|
296
537
|
if event['redirectResponse']
|
|
297
538
|
# We want to emit a response and requestfinished for the
|
|
@@ -303,39 +544,55 @@ class Puppeteer::NetworkManager
|
|
|
303
544
|
# response/requestfinished.
|
|
304
545
|
redirect_response_extra_info = nil
|
|
305
546
|
if event['redirectHasExtraInfo']
|
|
306
|
-
redirect_response_extra_info = @network_event_manager.response_extra_info(
|
|
547
|
+
redirect_response_extra_info = @network_event_manager.response_extra_info(network_request_id).shift
|
|
307
548
|
unless redirect_response_extra_info
|
|
308
549
|
redirect_info = RedirectInfo.new(
|
|
309
550
|
event: event,
|
|
310
551
|
fetch_request_id: fetch_request_id,
|
|
311
552
|
)
|
|
312
|
-
@network_event_manager.enqueue_redirect_info(
|
|
553
|
+
@network_event_manager.enqueue_redirect_info(network_request_id, redirect_info)
|
|
313
554
|
return
|
|
314
555
|
end
|
|
315
556
|
end
|
|
316
557
|
|
|
317
558
|
# If we connect late to the target, we could have missed the
|
|
318
559
|
# requestWillBeSent event.
|
|
319
|
-
if_present(@network_event_manager.get_request(
|
|
560
|
+
if_present(@network_event_manager.get_request(network_request_id)) do |request|
|
|
320
561
|
handle_request_redirect(request, event['redirectResponse'], redirect_response_extra_info)
|
|
321
562
|
redirect_chain = request.internal.redirect_chain
|
|
563
|
+
if_present(@network_event_manager.request_extra_info(network_request_id).shift) do |extra_info|
|
|
564
|
+
request.update_headers(extra_info['headers'])
|
|
565
|
+
end
|
|
322
566
|
end
|
|
323
567
|
end
|
|
324
568
|
frame = if_present(event['frameId']) { |frame_id| @frame_manager.frame(frame_id) }
|
|
325
|
-
request = Puppeteer::HTTPRequest.new(
|
|
326
|
-
@network_event_manager.
|
|
569
|
+
request = Puppeteer::HTTPRequest.new(client, frame, fetch_request_id, @user_request_interception_enabled, event, redirect_chain)
|
|
570
|
+
if_present(@network_event_manager.request_extra_info(network_request_id).shift) do |extra_info|
|
|
571
|
+
request.update_headers(extra_info['headers'])
|
|
572
|
+
end
|
|
573
|
+
request.internal.from_memory_cache = from_memory_cache
|
|
574
|
+
@network_event_manager.store_request(network_request_id, request)
|
|
327
575
|
emit_event(NetworkManagerEmittedEvents::Request, request)
|
|
328
576
|
begin
|
|
329
|
-
request.finalize_interceptions
|
|
577
|
+
with_interception_lock { request.finalize_interceptions }
|
|
330
578
|
rescue => err
|
|
331
579
|
debug_puts(err)
|
|
332
580
|
end
|
|
333
581
|
end
|
|
334
582
|
|
|
335
|
-
private def handle_request_served_from_cache(event)
|
|
336
|
-
|
|
583
|
+
private def handle_request_served_from_cache(event, client)
|
|
584
|
+
network_request_id = event['requestId']
|
|
585
|
+
request = @network_event_manager.get_request(network_request_id)
|
|
586
|
+
request_will_be_sent_event = @network_event_manager.get_request_will_be_sent(network_request_id)
|
|
337
587
|
if request
|
|
338
588
|
request.internal.from_memory_cache = true
|
|
589
|
+
elsif request_will_be_sent_event
|
|
590
|
+
handle_request(request_will_be_sent_event, nil, from_memory_cache: true, client: client)
|
|
591
|
+
request = @network_event_manager.get_request(network_request_id)
|
|
592
|
+
end
|
|
593
|
+
unless request
|
|
594
|
+
debug_puts("Request #{event['requestId']} was served from cache but we could not find the corresponding request object")
|
|
595
|
+
return
|
|
339
596
|
end
|
|
340
597
|
emit_event(NetworkManagerEmittedEvents::RequestServedFromCache, request)
|
|
341
598
|
end
|
|
@@ -343,7 +600,7 @@ class Puppeteer::NetworkManager
|
|
|
343
600
|
# @param request [Puppeteer::HTTPRequest]
|
|
344
601
|
# @param response_payload [Hash]
|
|
345
602
|
private def handle_request_redirect(request, response_payload, extra_info)
|
|
346
|
-
response = Puppeteer::HTTPResponse.new(
|
|
603
|
+
response = Puppeteer::HTTPResponse.new(request.client, request, response_payload, extra_info)
|
|
347
604
|
request.internal.response = response
|
|
348
605
|
request.internal.redirect_chain << request
|
|
349
606
|
response.internal.body_loaded_promise.reject(Puppeteer::HTTPResponse::Redirected.new)
|
|
@@ -352,122 +609,146 @@ class Puppeteer::NetworkManager
|
|
|
352
609
|
emit_event(NetworkManagerEmittedEvents::RequestFinished, request)
|
|
353
610
|
end
|
|
354
611
|
|
|
355
|
-
private def emit_response_event(response_received_event, extra_info)
|
|
356
|
-
request = @network_event_manager.get_request(
|
|
612
|
+
private def emit_response_event(response_received_event, extra_info, network_request_id:)
|
|
613
|
+
request = @network_event_manager.get_request(network_request_id)
|
|
357
614
|
# FileUpload sends a response without a matching request.
|
|
358
615
|
return unless request
|
|
359
616
|
|
|
360
|
-
unless @network_event_manager.response_extra_info(
|
|
617
|
+
unless @network_event_manager.response_extra_info(network_request_id).empty?
|
|
361
618
|
debug_puts("Unexpected extraInfo events for request #{response_received_event['requestId']}")
|
|
362
619
|
end
|
|
363
620
|
|
|
364
|
-
|
|
621
|
+
# Chromium sends wrong extraInfo events for responses served from cache.
|
|
622
|
+
# @see https://crbug.com/1340398
|
|
623
|
+
if response_received_event.dig('response', 'fromDiskCache')
|
|
624
|
+
extra_info = nil
|
|
625
|
+
end
|
|
626
|
+
|
|
627
|
+
response = Puppeteer::HTTPResponse.new(request.client, request, response_received_event['response'], extra_info)
|
|
365
628
|
request.internal.response = response
|
|
366
629
|
emit_event(NetworkManagerEmittedEvents::Response, response)
|
|
367
630
|
end
|
|
368
631
|
|
|
632
|
+
private def adopt_cdp_session_if_needed(client, request)
|
|
633
|
+
return if client == request.client
|
|
634
|
+
|
|
635
|
+
request.internal.client = client
|
|
636
|
+
end
|
|
637
|
+
|
|
369
638
|
# @param event [Hash]
|
|
370
|
-
private def handle_response_received(event)
|
|
371
|
-
|
|
639
|
+
private def handle_response_received(event, client)
|
|
640
|
+
network_request_id = event['requestId']
|
|
641
|
+
request = @network_event_manager.get_request(network_request_id)
|
|
372
642
|
extra_info = nil
|
|
373
643
|
if request && !request.internal.from_memory_cache? && event['hasExtraInfo']
|
|
374
|
-
extra_info = @network_event_manager.response_extra_info(
|
|
644
|
+
extra_info = @network_event_manager.response_extra_info(network_request_id).shift
|
|
375
645
|
|
|
376
646
|
unless extra_info
|
|
377
647
|
# Wait until we get the corresponding ExtraInfo event.
|
|
378
|
-
@network_event_manager.enqueue_event_group(
|
|
648
|
+
@network_event_manager.enqueue_event_group(network_request_id, QueuedEventGroup.new(response_received_event: event))
|
|
379
649
|
return
|
|
380
650
|
end
|
|
381
651
|
end
|
|
382
|
-
emit_response_event(event, extra_info)
|
|
652
|
+
emit_response_event(event, extra_info, network_request_id: network_request_id)
|
|
383
653
|
end
|
|
384
654
|
|
|
385
|
-
private def handle_response_received_extra_info(event)
|
|
655
|
+
private def handle_response_received_extra_info(event, client)
|
|
656
|
+
network_request_id = event['requestId']
|
|
386
657
|
# We may have skipped a redirect response/request pair due to waiting for
|
|
387
658
|
# this ExtraInfo event. If so, continue that work now that we have the
|
|
388
659
|
# request.
|
|
389
|
-
if_present(@network_event_manager.take_queued_redirect_info(
|
|
390
|
-
@network_event_manager.response_extra_info(
|
|
391
|
-
handle_request(redirect_info.event, redirect_info)
|
|
660
|
+
if_present(@network_event_manager.take_queued_redirect_info(network_request_id)) do |redirect_info|
|
|
661
|
+
@network_event_manager.response_extra_info(network_request_id) << event
|
|
662
|
+
handle_request(redirect_info.event, redirect_info.fetch_request_id, client: client)
|
|
392
663
|
return
|
|
393
664
|
end
|
|
394
665
|
|
|
395
666
|
# We may have skipped response and loading events because we didn't have
|
|
396
667
|
# this ExtraInfo event yet. If so, emit those events now.
|
|
397
|
-
if_present(@network_event_manager.get_queued_event_group(
|
|
398
|
-
@network_event_manager.forget_queued_event_group(
|
|
399
|
-
emit_response_event(queued_events.response_received_event, event)
|
|
668
|
+
if_present(@network_event_manager.get_queued_event_group(network_request_id)) do |queued_events|
|
|
669
|
+
@network_event_manager.forget_queued_event_group(network_request_id)
|
|
670
|
+
emit_response_event(queued_events.response_received_event, event, network_request_id: network_request_id)
|
|
400
671
|
if_present(queued_events.loading_finished_event) do |loading_finished_event|
|
|
401
|
-
emit_loading_finished(loading_finished_event)
|
|
672
|
+
emit_loading_finished(loading_finished_event, network_request_id: network_request_id, client: client)
|
|
402
673
|
end
|
|
403
674
|
if_present(queued_events.loading_failed_event) do |loading_failed_event|
|
|
404
|
-
emit_loading_failed(loading_failed_event)
|
|
675
|
+
emit_loading_failed(loading_failed_event, network_request_id: network_request_id, client: client)
|
|
405
676
|
end
|
|
406
677
|
return
|
|
407
678
|
end
|
|
408
679
|
|
|
409
680
|
# Wait until we get another event that can use this ExtraInfo event.
|
|
410
|
-
@network_event_manager.
|
|
681
|
+
return unless @network_event_manager.get_request(network_request_id)
|
|
682
|
+
|
|
683
|
+
@network_event_manager.response_extra_info(network_request_id) << event
|
|
411
684
|
end
|
|
412
685
|
|
|
413
686
|
private def forget_request(request, forget_events)
|
|
414
687
|
request_id = request.internal.request_id
|
|
688
|
+
network_request_id = request_id
|
|
415
689
|
interception_id = request.internal.interception_id
|
|
690
|
+
auth_request_id = interception_id
|
|
416
691
|
|
|
417
|
-
@network_event_manager.forget_request(
|
|
418
|
-
@attempted_authentications.delete(
|
|
692
|
+
@network_event_manager.forget_request(network_request_id)
|
|
693
|
+
@attempted_authentications.delete(auth_request_id)
|
|
419
694
|
if forget_events
|
|
420
|
-
@network_event_manager.forget(
|
|
695
|
+
@network_event_manager.forget(network_request_id)
|
|
421
696
|
end
|
|
422
697
|
end
|
|
423
698
|
|
|
424
|
-
private def handle_loading_finished(event)
|
|
699
|
+
private def handle_loading_finished(event, client)
|
|
700
|
+
network_request_id = event['requestId']
|
|
425
701
|
# If the response event for this request is still waiting on a
|
|
426
702
|
# corresponding ExtraInfo event, then wait to emit this event too.
|
|
427
|
-
queued_events = @network_event_manager.get_queued_event_group(
|
|
703
|
+
queued_events = @network_event_manager.get_queued_event_group(network_request_id)
|
|
428
704
|
if queued_events
|
|
429
705
|
queued_events.loading_finished_event = event
|
|
430
706
|
else
|
|
431
|
-
emit_loading_finished(event)
|
|
707
|
+
emit_loading_finished(event, network_request_id: network_request_id, client: client)
|
|
432
708
|
end
|
|
433
709
|
end
|
|
434
710
|
|
|
435
|
-
private def emit_loading_finished(event)
|
|
436
|
-
request = @network_event_manager.get_request(
|
|
711
|
+
private def emit_loading_finished(event, network_request_id:, client:)
|
|
712
|
+
request = @network_event_manager.get_request(network_request_id)
|
|
437
713
|
# For certain requestIds we never receive requestWillBeSent event.
|
|
438
714
|
# @see https://crbug.com/750469
|
|
439
715
|
return unless request
|
|
440
716
|
|
|
717
|
+
adopt_cdp_session_if_needed(client, request)
|
|
718
|
+
|
|
441
719
|
# Under certain conditions we never get the Network.responseReceived
|
|
442
720
|
# event from protocol. @see https://crbug.com/883475
|
|
443
721
|
if_present(request.response) do |response|
|
|
444
|
-
response.internal.body_loaded_promise.
|
|
722
|
+
response.internal.body_loaded_promise.resolve(nil)
|
|
445
723
|
end
|
|
446
724
|
|
|
447
725
|
forget_request(request, true)
|
|
448
726
|
emit_event(NetworkManagerEmittedEvents::RequestFinished, request)
|
|
449
727
|
end
|
|
450
728
|
|
|
451
|
-
private def handle_loading_failed(event)
|
|
729
|
+
private def handle_loading_failed(event, client)
|
|
730
|
+
network_request_id = event['requestId']
|
|
452
731
|
# If the response event for this request is still waiting on a
|
|
453
732
|
# corresponding ExtraInfo event, then wait to emit this event too.
|
|
454
|
-
queued_events = @network_event_manager.get_queued_event_group(
|
|
733
|
+
queued_events = @network_event_manager.get_queued_event_group(network_request_id)
|
|
455
734
|
if queued_events
|
|
456
735
|
queued_events.loading_failed_event = event
|
|
457
736
|
else
|
|
458
|
-
emit_loading_failed(event)
|
|
737
|
+
emit_loading_failed(event, network_request_id: network_request_id, client: client)
|
|
459
738
|
end
|
|
460
739
|
end
|
|
461
740
|
|
|
462
|
-
private def emit_loading_failed(event)
|
|
463
|
-
request = @network_event_manager.get_request(
|
|
741
|
+
private def emit_loading_failed(event, network_request_id:, client:)
|
|
742
|
+
request = @network_event_manager.get_request(network_request_id)
|
|
464
743
|
# For certain requestIds we never receive requestWillBeSent event.
|
|
465
744
|
# @see https://crbug.com/750469
|
|
466
745
|
return unless request
|
|
467
746
|
|
|
747
|
+
adopt_cdp_session_if_needed(client, request)
|
|
748
|
+
|
|
468
749
|
request.internal.failure_text = event['errorText']
|
|
469
750
|
if_present(request.response) do |response|
|
|
470
|
-
response.internal.body_loaded_promise.
|
|
751
|
+
response.internal.body_loaded_promise.resolve(nil)
|
|
471
752
|
end
|
|
472
753
|
forget_request(request, true)
|
|
473
754
|
emit_event(NetworkManagerEmittedEvents::RequestFailed, request)
|