ferrum 0.17.2 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +4 -2
- data/lib/ferrum/accessibility/ax_node.rb +108 -0
- data/lib/ferrum/accessibility.rb +106 -0
- data/lib/ferrum/browser/binary.rb +42 -2
- data/lib/ferrum/browser/command.rb +20 -2
- data/lib/ferrum/browser/options/base.rb +66 -0
- data/lib/ferrum/browser/options/chrome.rb +35 -2
- data/lib/ferrum/browser/options.rb +41 -2
- data/lib/ferrum/browser/process/killer.rb +188 -0
- data/lib/ferrum/browser/process.rb +128 -72
- data/lib/ferrum/browser/xvfb.rb +24 -0
- data/lib/ferrum/browser.rb +47 -17
- data/lib/ferrum/client/subscriber.rb +65 -2
- data/lib/ferrum/client/web_socket.rb +126 -13
- data/lib/ferrum/client.rb +225 -12
- data/lib/ferrum/context.rb +113 -7
- data/lib/ferrum/contexts.rb +188 -19
- data/lib/ferrum/cookies/cookie.rb +6 -0
- data/lib/ferrum/cookies.rb +5 -0
- data/lib/ferrum/dialog.rb +18 -2
- data/lib/ferrum/downloads.rb +54 -1
- data/lib/ferrum/errors.rb +103 -4
- data/lib/ferrum/frame/dom.rb +65 -0
- data/lib/ferrum/frame/runtime.rb +57 -15
- data/lib/ferrum/frame.rb +58 -1
- data/lib/ferrum/headers.rb +6 -0
- data/lib/ferrum/interceptable.rb +83 -0
- data/lib/ferrum/keyboard.rb +25 -0
- data/lib/ferrum/mouse.rb +6 -0
- data/lib/ferrum/network/auth_request.rb +81 -2
- data/lib/ferrum/network/error.rb +15 -0
- data/lib/ferrum/network/exchange.rb +10 -0
- data/lib/ferrum/network/intercepted_request.rb +94 -2
- data/lib/ferrum/network/request.rb +1 -1
- data/lib/ferrum/network/response.rb +2 -0
- data/lib/ferrum/network.rb +121 -9
- data/lib/ferrum/node.rb +305 -15
- data/lib/ferrum/page/animation.rb +5 -0
- data/lib/ferrum/page/frames.rb +69 -7
- data/lib/ferrum/page/screencast.rb +5 -0
- data/lib/ferrum/page/screenshot.rb +55 -23
- data/lib/ferrum/page/stream.rb +71 -0
- data/lib/ferrum/page/tracing.rb +6 -0
- data/lib/ferrum/page.rb +148 -45
- data/lib/ferrum/proxy.rb +52 -2
- data/lib/ferrum/rgba.rb +10 -0
- data/lib/ferrum/target.rb +130 -3
- data/lib/ferrum/utils/attempt.rb +20 -0
- data/lib/ferrum/utils/elapsed_time.rb +38 -0
- data/lib/ferrum/utils/event.rb +14 -0
- data/lib/ferrum/utils/platform.rb +21 -0
- data/lib/ferrum/utils/thread.rb +15 -1
- data/lib/ferrum/version.rb +1 -1
- data/lib/ferrum/worker.rb +125 -0
- data/lib/ferrum.rb +7 -0
- metadata +6 -16
- data/lib/ferrum/browser/options/firefox.rb +0 -38
data/lib/ferrum/mouse.rb
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Ferrum
|
|
4
|
+
#
|
|
5
|
+
# Simulates mouse input for a page via the CDP `Input.dispatchMouseEvent`
|
|
6
|
+
# command, and page scrolling via JavaScript. Keeps track of the current
|
|
7
|
+
# pointer position and pressed buttons so `move`, `down`, `up` and `click`
|
|
8
|
+
# can be composed while reporting consistent state to Chrome.
|
|
9
|
+
#
|
|
4
10
|
class Mouse
|
|
5
11
|
CLICK_WAIT = ENV.fetch("FERRUM_CLICK_WAIT", 0.1).to_f
|
|
6
12
|
BUTTON_MASKS = {
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
module Ferrum
|
|
4
4
|
class Network
|
|
5
|
+
#
|
|
6
|
+
# Represents an HTTP authentication challenge (basic or digest auth
|
|
7
|
+
# prompt) raised by the browser via `Fetch.authRequired`, which can be
|
|
8
|
+
# answered with credentials or canceled.
|
|
9
|
+
#
|
|
5
10
|
class AuthRequest
|
|
6
11
|
attr_accessor :request_id, :frame_id, :resource_type
|
|
7
12
|
|
|
@@ -14,47 +19,121 @@ module Ferrum
|
|
|
14
19
|
@request = params["request"]
|
|
15
20
|
end
|
|
16
21
|
|
|
22
|
+
#
|
|
23
|
+
# Whether this request is for the navigation of a frame, as opposed
|
|
24
|
+
# to a subresource (script, image, XHR, etc).
|
|
25
|
+
#
|
|
26
|
+
# @return [Boolean]
|
|
27
|
+
#
|
|
17
28
|
def navigation_request?
|
|
18
29
|
@params["isNavigationRequest"]
|
|
19
30
|
end
|
|
20
31
|
|
|
32
|
+
#
|
|
33
|
+
# Whether the authentication challenge's source matches the given
|
|
34
|
+
# type.
|
|
35
|
+
#
|
|
36
|
+
# @param [String, Symbol] source
|
|
37
|
+
# One of `:server` or `:proxy`.
|
|
38
|
+
#
|
|
39
|
+
# @return [Boolean]
|
|
40
|
+
#
|
|
21
41
|
def auth_challenge?(source)
|
|
22
42
|
@params.dig("authChallenge", "source")&.downcase&.to_s == source.to_s
|
|
23
43
|
end
|
|
24
44
|
|
|
25
|
-
|
|
26
|
-
|
|
45
|
+
#
|
|
46
|
+
# Whether the request's URL matches the given pattern.
|
|
47
|
+
#
|
|
48
|
+
# @param [String, Regexp] pattern
|
|
49
|
+
#
|
|
50
|
+
# @return [Boolean]
|
|
51
|
+
#
|
|
52
|
+
def match?(pattern)
|
|
53
|
+
case url
|
|
54
|
+
when pattern
|
|
55
|
+
true
|
|
56
|
+
else
|
|
57
|
+
false
|
|
58
|
+
end
|
|
27
59
|
end
|
|
28
60
|
|
|
61
|
+
#
|
|
62
|
+
# Responds to the authentication challenge, e.g. with credentials or by
|
|
63
|
+
# canceling it.
|
|
64
|
+
#
|
|
65
|
+
# @param [Hash] options
|
|
66
|
+
#
|
|
67
|
+
# @option options [String] :response
|
|
68
|
+
# One of `"Default"`, `"CancelAuth"`, or `"ProvideCredentials"`.
|
|
69
|
+
#
|
|
70
|
+
# @option options [String] :username
|
|
71
|
+
#
|
|
72
|
+
# @option options [String] :password
|
|
73
|
+
#
|
|
29
74
|
def continue(**options)
|
|
30
75
|
options = options.merge(requestId: request_id)
|
|
31
76
|
@page.command("Fetch.continueWithAuth", **options)
|
|
32
77
|
end
|
|
33
78
|
|
|
79
|
+
#
|
|
80
|
+
# Cancels the authentication challenge and the request.
|
|
81
|
+
#
|
|
34
82
|
def abort
|
|
35
83
|
@page.command("Fetch.failRequest", requestId: request_id, errorReason: "BlockedByClient")
|
|
36
84
|
end
|
|
37
85
|
|
|
86
|
+
#
|
|
87
|
+
# The URL for the request.
|
|
88
|
+
#
|
|
89
|
+
# @return [String]
|
|
90
|
+
#
|
|
38
91
|
def url
|
|
39
92
|
@request["url"]
|
|
40
93
|
end
|
|
41
94
|
|
|
95
|
+
#
|
|
96
|
+
# The request method.
|
|
97
|
+
#
|
|
98
|
+
# @return [String]
|
|
99
|
+
#
|
|
42
100
|
def method
|
|
43
101
|
@request["method"]
|
|
44
102
|
end
|
|
45
103
|
|
|
104
|
+
#
|
|
105
|
+
# The request headers.
|
|
106
|
+
#
|
|
107
|
+
# @return [Hash{String => String}]
|
|
108
|
+
#
|
|
46
109
|
def headers
|
|
47
110
|
@request["headers"]
|
|
48
111
|
end
|
|
49
112
|
|
|
113
|
+
#
|
|
114
|
+
# The request's initial priority, one of `"VeryLow"`, `"Low"`,
|
|
115
|
+
# `"Medium"`, `"High"`, or `"VeryHigh"`.
|
|
116
|
+
#
|
|
117
|
+
# @return [String]
|
|
118
|
+
#
|
|
50
119
|
def initial_priority
|
|
51
120
|
@request["initialPriority"]
|
|
52
121
|
end
|
|
53
122
|
|
|
123
|
+
#
|
|
124
|
+
# The request's referrer policy.
|
|
125
|
+
#
|
|
126
|
+
# @return [String]
|
|
127
|
+
#
|
|
54
128
|
def referrer_policy
|
|
55
129
|
@request["referrerPolicy"]
|
|
56
130
|
end
|
|
57
131
|
|
|
132
|
+
#
|
|
133
|
+
# Inspects the auth request.
|
|
134
|
+
#
|
|
135
|
+
# @return [String]
|
|
136
|
+
#
|
|
58
137
|
def inspect
|
|
59
138
|
"#<#{self.class} " \
|
|
60
139
|
"@request_id=#{@request_id.inspect} " \
|
data/lib/ferrum/network/error.rb
CHANGED
|
@@ -2,15 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
module Ferrum
|
|
4
4
|
class Network
|
|
5
|
+
#
|
|
6
|
+
# Represents a network-level loading error reported for a request,
|
|
7
|
+
# e.g. via `Network.loadingFailed`. Distinct from the Ruby exceptions
|
|
8
|
+
# defined in `ferrum/errors.rb`.
|
|
9
|
+
#
|
|
5
10
|
class Error
|
|
6
11
|
attr_writer :canceled
|
|
7
12
|
attr_reader :time, :timestamp
|
|
8
13
|
attr_accessor :id, :url, :type, :error_text, :monotonic_time, :description
|
|
9
14
|
|
|
15
|
+
# Whether the request was canceled.
|
|
16
|
+
#
|
|
17
|
+
# @return [Boolean]
|
|
10
18
|
def canceled?
|
|
11
19
|
@canceled
|
|
12
20
|
end
|
|
13
21
|
|
|
22
|
+
# Sets the error's timestamp, deriving {#time} from it.
|
|
23
|
+
#
|
|
24
|
+
# @param [Float] value
|
|
25
|
+
# Timestamp in milliseconds since epoch, as reported by
|
|
26
|
+
# `Log.entryAdded`.
|
|
27
|
+
#
|
|
28
|
+
# @return [Float]
|
|
14
29
|
def timestamp=(value)
|
|
15
30
|
@timestamp = value
|
|
16
31
|
@time = Time.strptime((value / 1000).to_s, "%s")
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
module Ferrum
|
|
4
4
|
class Network
|
|
5
|
+
#
|
|
6
|
+
# Pairs a {Request} with its eventual {Response} or {Error} (and, if
|
|
7
|
+
# interception is enabled, its {InterceptedRequest}), representing the
|
|
8
|
+
# full lifecycle of a single network request.
|
|
9
|
+
#
|
|
5
10
|
class Exchange
|
|
6
11
|
# ID of the request.
|
|
7
12
|
#
|
|
@@ -34,6 +39,11 @@ module Ferrum
|
|
|
34
39
|
# @return Boolean
|
|
35
40
|
attr_accessor :unknown
|
|
36
41
|
|
|
42
|
+
# The raw `Network.requestWillBeSentExtraInfo` params for the request,
|
|
43
|
+
# if it arrived before `request` was set.
|
|
44
|
+
#
|
|
45
|
+
# @return [Hash, nil]
|
|
46
|
+
#
|
|
37
47
|
# @api private
|
|
38
48
|
attr_accessor :request_extra_info
|
|
39
49
|
|
|
@@ -5,6 +5,11 @@ require "base64"
|
|
|
5
5
|
|
|
6
6
|
module Ferrum
|
|
7
7
|
class Network
|
|
8
|
+
#
|
|
9
|
+
# Represents a request paused by request interception (`Fetch.enable`),
|
|
10
|
+
# allowing it to be continued as is or with overrides, fulfilled with a
|
|
11
|
+
# fake response, or aborted before it reaches the network.
|
|
12
|
+
#
|
|
8
13
|
class InterceptedRequest
|
|
9
14
|
include RequestParams
|
|
10
15
|
|
|
@@ -21,18 +26,62 @@ module Ferrum
|
|
|
21
26
|
@network_id = params["networkId"]
|
|
22
27
|
end
|
|
23
28
|
|
|
29
|
+
#
|
|
30
|
+
# Whether the request's current status matches the given value.
|
|
31
|
+
#
|
|
32
|
+
# @param [String, Symbol] value
|
|
33
|
+
# One of `:responded`, `:continued`, `:aborted`.
|
|
34
|
+
#
|
|
35
|
+
# @return [Boolean]
|
|
36
|
+
#
|
|
24
37
|
def status?(value)
|
|
25
38
|
@status == value.to_sym
|
|
26
39
|
end
|
|
27
40
|
|
|
41
|
+
#
|
|
42
|
+
# Whether this request is for the navigation of a frame, as opposed
|
|
43
|
+
# to a subresource (script, image, XHR, etc).
|
|
44
|
+
#
|
|
45
|
+
# @return [Boolean]
|
|
46
|
+
#
|
|
28
47
|
def navigation_request?
|
|
29
48
|
@params["isNavigationRequest"]
|
|
30
49
|
end
|
|
31
50
|
|
|
32
|
-
|
|
33
|
-
|
|
51
|
+
#
|
|
52
|
+
# Whether the request's URL matches the given pattern.
|
|
53
|
+
#
|
|
54
|
+
# @param [String, Regexp] pattern
|
|
55
|
+
#
|
|
56
|
+
# @return [Boolean]
|
|
57
|
+
#
|
|
58
|
+
def match?(pattern)
|
|
59
|
+
case url
|
|
60
|
+
when pattern
|
|
61
|
+
true
|
|
62
|
+
else
|
|
63
|
+
false
|
|
64
|
+
end
|
|
34
65
|
end
|
|
35
66
|
|
|
67
|
+
#
|
|
68
|
+
# Fulfills the intercepted request with a fake response instead of
|
|
69
|
+
# letting it reach the network.
|
|
70
|
+
#
|
|
71
|
+
# @param [Hash] options
|
|
72
|
+
#
|
|
73
|
+
# @option options [Integer] :responseCode
|
|
74
|
+
# HTTP status code to respond with, `200` by default.
|
|
75
|
+
#
|
|
76
|
+
# @option options [String] :body
|
|
77
|
+
# Response body.
|
|
78
|
+
#
|
|
79
|
+
# @option options [Hash] :responseHeaders
|
|
80
|
+
# Response headers.
|
|
81
|
+
#
|
|
82
|
+
# @example
|
|
83
|
+
# request.respond(body: "Lorem ipsum")
|
|
84
|
+
#
|
|
36
85
|
def respond(**options)
|
|
37
86
|
has_body = options.key?(:body)
|
|
38
87
|
headers = has_body ? { "content-length" => options.fetch(:body, "").length } : {}
|
|
@@ -46,25 +95,68 @@ module Ferrum
|
|
|
46
95
|
@client.command("Fetch.fulfillRequest", async: true, **options)
|
|
47
96
|
end
|
|
48
97
|
|
|
98
|
+
#
|
|
99
|
+
# Continues the intercepted request, letting it reach the network as
|
|
100
|
+
# is, or with the given overrides.
|
|
101
|
+
#
|
|
102
|
+
# @param [Hash] options
|
|
103
|
+
#
|
|
104
|
+
# @option options [String] :url
|
|
105
|
+
# Overrides the request's URL.
|
|
106
|
+
#
|
|
107
|
+
# @option options [String] :method
|
|
108
|
+
# Overrides the request's method.
|
|
109
|
+
#
|
|
110
|
+
# @option options [String] :postData
|
|
111
|
+
# Overrides the request's post data.
|
|
112
|
+
#
|
|
113
|
+
# @option options [Hash] :headers
|
|
114
|
+
# Overrides the request's headers.
|
|
115
|
+
#
|
|
116
|
+
# @example
|
|
117
|
+
# request.continue
|
|
118
|
+
#
|
|
49
119
|
def continue(**options)
|
|
50
120
|
options = options.merge(requestId: request_id)
|
|
51
121
|
@status = :continued
|
|
52
122
|
@client.command("Fetch.continueRequest", async: true, **options)
|
|
53
123
|
end
|
|
54
124
|
|
|
125
|
+
#
|
|
126
|
+
# Aborts the intercepted request.
|
|
127
|
+
#
|
|
128
|
+
# @example
|
|
129
|
+
# request.abort
|
|
130
|
+
#
|
|
55
131
|
def abort
|
|
56
132
|
@status = :aborted
|
|
57
133
|
@client.command("Fetch.failRequest", async: true, requestId: request_id, errorReason: "BlockedByClient")
|
|
58
134
|
end
|
|
59
135
|
|
|
136
|
+
#
|
|
137
|
+
# The request's initial priority, one of `"VeryLow"`, `"Low"`,
|
|
138
|
+
# `"Medium"`, `"High"`, or `"VeryHigh"`.
|
|
139
|
+
#
|
|
140
|
+
# @return [String]
|
|
141
|
+
#
|
|
60
142
|
def initial_priority
|
|
61
143
|
@request["initialPriority"]
|
|
62
144
|
end
|
|
63
145
|
|
|
146
|
+
#
|
|
147
|
+
# The request's referrer policy.
|
|
148
|
+
#
|
|
149
|
+
# @return [String]
|
|
150
|
+
#
|
|
64
151
|
def referrer_policy
|
|
65
152
|
@request["referrerPolicy"]
|
|
66
153
|
end
|
|
67
154
|
|
|
155
|
+
#
|
|
156
|
+
# Inspects the intercepted request.
|
|
157
|
+
#
|
|
158
|
+
# @return [String]
|
|
159
|
+
#
|
|
68
160
|
def inspect
|
|
69
161
|
"#<#{self.class} " \
|
|
70
162
|
"@request_id=#{@request_id.inspect} " \
|
data/lib/ferrum/network.rb
CHANGED
|
@@ -8,6 +8,13 @@ require "ferrum/network/request"
|
|
|
8
8
|
require "ferrum/network/response"
|
|
9
9
|
|
|
10
10
|
module Ferrum
|
|
11
|
+
#
|
|
12
|
+
# Tracks a page's network activity, exposing it as a list of {#traffic}
|
|
13
|
+
# {Network::Exchange}s built from the underlying CDP `Network.*` events.
|
|
14
|
+
# Also provides request interception/authorization (`intercept`,
|
|
15
|
+
# `authorize`, `blacklist=`/`whitelist=`) and network condition emulation
|
|
16
|
+
# (`emulate_network_conditions`, `offline_mode`).
|
|
17
|
+
#
|
|
11
18
|
class Network
|
|
12
19
|
CLEAR_TYPE = %i[traffic cache].freeze
|
|
13
20
|
AUTHORIZE_TYPE = %i[server proxy].freeze
|
|
@@ -38,6 +45,7 @@ module Ferrum
|
|
|
38
45
|
@exchange = nil
|
|
39
46
|
@blacklist = nil
|
|
40
47
|
@whitelist = nil
|
|
48
|
+
@mutex = Mutex.new
|
|
41
49
|
end
|
|
42
50
|
|
|
43
51
|
#
|
|
@@ -81,18 +89,45 @@ module Ferrum
|
|
|
81
89
|
raise TimeoutError unless result
|
|
82
90
|
end
|
|
83
91
|
|
|
92
|
+
#
|
|
93
|
+
# Whether the network is idle, i.e. no more than `connections`
|
|
94
|
+
# connections are still pending.
|
|
95
|
+
#
|
|
96
|
+
# @param [Integer] connections
|
|
97
|
+
# How many connections are allowed for network to be idling.
|
|
98
|
+
#
|
|
99
|
+
# @return [Boolean]
|
|
100
|
+
#
|
|
84
101
|
def idle?(connections = 0)
|
|
85
102
|
pending_connections <= connections
|
|
86
103
|
end
|
|
87
104
|
|
|
105
|
+
#
|
|
106
|
+
# Total number of network connections seen since the traffic was last
|
|
107
|
+
# cleared.
|
|
108
|
+
#
|
|
109
|
+
# @return [Integer]
|
|
110
|
+
#
|
|
88
111
|
def total_connections
|
|
89
112
|
@traffic.size
|
|
90
113
|
end
|
|
91
114
|
|
|
115
|
+
#
|
|
116
|
+
# Number of network connections that have finished, i.e. were blocked, got
|
|
117
|
+
# a loaded response, errored, or are otherwise no longer pending.
|
|
118
|
+
#
|
|
119
|
+
# @return [Integer]
|
|
120
|
+
#
|
|
92
121
|
def finished_connections
|
|
93
122
|
@traffic.count(&:finished?)
|
|
94
123
|
end
|
|
95
124
|
|
|
125
|
+
#
|
|
126
|
+
# Number of network connections that are still pending, i.e. haven't
|
|
127
|
+
# finished yet.
|
|
128
|
+
#
|
|
129
|
+
# @return [Integer]
|
|
130
|
+
#
|
|
96
131
|
def pending_connections
|
|
97
132
|
total_connections - finished_connections
|
|
98
133
|
end
|
|
@@ -164,12 +199,38 @@ module Ferrum
|
|
|
164
199
|
true
|
|
165
200
|
end
|
|
166
201
|
|
|
202
|
+
#
|
|
203
|
+
# Sets a list of patterns for URLs that should be blocked from loading.
|
|
204
|
+
# Aborts any request whose URL matches one of the given patterns, and
|
|
205
|
+
# continues all others. Can't be used together with `whitelist=`.
|
|
206
|
+
#
|
|
207
|
+
# @param [String, Regexp, Array<String, Regexp>] patterns
|
|
208
|
+
# One or more patterns matched against the request's URL, see
|
|
209
|
+
# {InterceptedRequest#match?}.
|
|
210
|
+
#
|
|
211
|
+
# @example
|
|
212
|
+
# browser.network.blacklist = /jquery/
|
|
213
|
+
# browser.go_to("https://example.com/")
|
|
214
|
+
#
|
|
167
215
|
def blacklist=(patterns)
|
|
168
216
|
@blacklist = Array(patterns)
|
|
169
217
|
blacklist_subscribe
|
|
170
218
|
end
|
|
171
219
|
alias blocklist= blacklist=
|
|
172
220
|
|
|
221
|
+
#
|
|
222
|
+
# Sets a list of patterns for URLs that are the only ones allowed to load.
|
|
223
|
+
# Continues any request whose URL matches one of the given patterns, and
|
|
224
|
+
# aborts all others. Can't be used together with `blacklist=`.
|
|
225
|
+
#
|
|
226
|
+
# @param [String, Regexp, Array<String, Regexp>] patterns
|
|
227
|
+
# One or more patterns matched against the request's URL, see
|
|
228
|
+
# {InterceptedRequest#match?}.
|
|
229
|
+
#
|
|
230
|
+
# @example
|
|
231
|
+
# browser.network.whitelist = /example/
|
|
232
|
+
# browser.go_to("https://example.com/")
|
|
233
|
+
#
|
|
173
234
|
def whitelist=(patterns)
|
|
174
235
|
@whitelist = Array(patterns)
|
|
175
236
|
whitelist_subscribe
|
|
@@ -268,6 +329,11 @@ module Ferrum
|
|
|
268
329
|
end
|
|
269
330
|
end
|
|
270
331
|
|
|
332
|
+
#
|
|
333
|
+
# Subscribes to the CDP events needed to keep track of `traffic`. Called
|
|
334
|
+
# once when the page is initialized.
|
|
335
|
+
#
|
|
336
|
+
# @api private
|
|
271
337
|
def subscribe
|
|
272
338
|
subscribe_request_will_be_sent
|
|
273
339
|
subscribe_response_received
|
|
@@ -276,6 +342,23 @@ module Ferrum
|
|
|
276
342
|
subscribe_log_entry_added
|
|
277
343
|
end
|
|
278
344
|
|
|
345
|
+
#
|
|
346
|
+
# Builds the `authChallengeResponse` sent back to Chrome for an
|
|
347
|
+
# authenticated request, used by `authorize`.
|
|
348
|
+
#
|
|
349
|
+
# @param [Array<String>] ids
|
|
350
|
+
# Request ids that were already given credentials, to avoid an infinite
|
|
351
|
+
# retry loop if the credentials are rejected.
|
|
352
|
+
#
|
|
353
|
+
# @param [String] request_id
|
|
354
|
+
#
|
|
355
|
+
# @param [String, nil] username
|
|
356
|
+
#
|
|
357
|
+
# @param [String, nil] password
|
|
358
|
+
#
|
|
359
|
+
# @return [Hash, nil]
|
|
360
|
+
#
|
|
361
|
+
# @api private
|
|
279
362
|
def authorized_response(ids, request_id, username, password)
|
|
280
363
|
if ids.include?(request_id)
|
|
281
364
|
{ response: "CancelAuth" }
|
|
@@ -286,14 +369,42 @@ module Ferrum
|
|
|
286
369
|
end
|
|
287
370
|
end
|
|
288
371
|
|
|
372
|
+
#
|
|
373
|
+
# Finds the exchanges in `traffic` with the given request id.
|
|
374
|
+
#
|
|
375
|
+
# @param [String] request_id
|
|
376
|
+
#
|
|
377
|
+
# @return [Array<Exchange>]
|
|
378
|
+
#
|
|
379
|
+
# @api private
|
|
289
380
|
def select(request_id)
|
|
290
381
|
@traffic.select { |e| e.id == request_id }
|
|
291
382
|
end
|
|
292
383
|
|
|
384
|
+
#
|
|
385
|
+
# Builds a new {Exchange} for the given request id and appends it to
|
|
386
|
+
# `traffic`.
|
|
387
|
+
#
|
|
388
|
+
# @param [String] id
|
|
389
|
+
#
|
|
390
|
+
# @return [Exchange]
|
|
391
|
+
#
|
|
392
|
+
# @api private
|
|
293
393
|
def build_exchange(id)
|
|
294
394
|
Network::Exchange.new(@page, id).tap { |e| @traffic << e }
|
|
295
395
|
end
|
|
296
396
|
|
|
397
|
+
# `Network.requestWillBeSent` and `Fetch.requestPaused` are handled on
|
|
398
|
+
# separate threads (see `Client::Subscriber`), so the "find the existing
|
|
399
|
+
# exchange for this id or build a new one" check has to be atomic,
|
|
400
|
+
# otherwise both threads can race past the `select` before either has
|
|
401
|
+
# appended, and end up building two exchanges for the same request.
|
|
402
|
+
#
|
|
403
|
+
# @api private
|
|
404
|
+
def find_or_build_exchange(id)
|
|
405
|
+
@mutex.synchronize { select(id).last || build_exchange(id) }
|
|
406
|
+
end
|
|
407
|
+
|
|
297
408
|
#
|
|
298
409
|
# Activates emulation of network conditions.
|
|
299
410
|
#
|
|
@@ -380,24 +491,25 @@ module Ferrum
|
|
|
380
491
|
|
|
381
492
|
# We can build exchange in two places, here on the event or when request
|
|
382
493
|
# is interrupted. So we have to be careful when to create new one. We
|
|
383
|
-
# create new exchange only if there's no with such id or there's, but
|
|
384
|
-
# it's filled with request which means this one is new but has response
|
|
385
|
-
# for a redirect. So we assign response from the params to previous
|
|
386
|
-
# exchange and build new exchange to assign this request to it.
|
|
387
|
-
exchange =
|
|
388
|
-
|
|
494
|
+
# create a new exchange only if there's no with such an id or there's, but
|
|
495
|
+
# it's filled with request which means this one is new but has a response
|
|
496
|
+
# for a redirect. So we assign a response from the params to the previous
|
|
497
|
+
# exchange and build a new exchange to assign this request to it.
|
|
498
|
+
exchange = @mutex.synchronize do
|
|
499
|
+
ex = select(request.id).last
|
|
500
|
+
ex.nil? || !ex.blank? ? build_exchange(request.id) : ex
|
|
501
|
+
end
|
|
389
502
|
request.headers.merge!(Hash(exchange.request_extra_info&.dig("headers")))
|
|
390
503
|
exchange.request = request
|
|
391
504
|
|
|
392
|
-
if exchange.navigation_request?(@page.main_frame
|
|
505
|
+
if exchange.navigation_request?(@page.main_frame&.id)
|
|
393
506
|
@exchange = exchange
|
|
394
507
|
classify_pending_exchanges(exchange.loader_id)
|
|
395
508
|
end
|
|
396
509
|
end
|
|
397
510
|
|
|
398
511
|
@page.on("Network.requestWillBeSentExtraInfo") do |params|
|
|
399
|
-
exchange =
|
|
400
|
-
exchange ||= build_exchange(params["requestId"])
|
|
512
|
+
exchange = find_or_build_exchange(params["requestId"])
|
|
401
513
|
exchange.request_extra_info = params
|
|
402
514
|
exchange.request&.headers&.merge!(params["headers"])
|
|
403
515
|
end
|