ferrum 0.17.2 → 0.18.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 +1 -1
- data/lib/ferrum/accessibility/ax_node.rb +108 -0
- data/lib/ferrum/accessibility.rb +106 -0
- data/lib/ferrum/browser/binary.rb +41 -0
- data/lib/ferrum/browser/command.rb +20 -0
- data/lib/ferrum/browser/options/base.rb +67 -0
- data/lib/ferrum/browser/options/chrome.rb +36 -1
- data/lib/ferrum/browser/options/firefox.rb +32 -0
- data/lib/ferrum/browser/options.rb +41 -2
- data/lib/ferrum/browser/process.rb +51 -0
- data/lib/ferrum/browser/xvfb.rb +24 -0
- data/lib/ferrum/browser.rb +32 -10
- data/lib/ferrum/client/subscriber.rb +58 -0
- data/lib/ferrum/client/web_socket.rb +62 -11
- data/lib/ferrum/client.rb +199 -8
- data/lib/ferrum/context.rb +97 -4
- data/lib/ferrum/contexts.rb +119 -9
- 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 +52 -0
- data/lib/ferrum/errors.rb +58 -3
- data/lib/ferrum/frame/dom.rb +17 -0
- data/lib/ferrum/frame/runtime.rb +48 -7
- data/lib/ferrum/frame.rb +58 -1
- data/lib/ferrum/headers.rb +6 -0
- data/lib/ferrum/interceptable.rb +62 -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 +52 -20
- data/lib/ferrum/page/stream.rb +56 -0
- data/lib/ferrum/page/tracing.rb +6 -0
- data/lib/ferrum/page.rb +139 -42
- data/lib/ferrum/proxy.rb +52 -2
- data/lib/ferrum/rgba.rb +10 -0
- data/lib/ferrum/target.rb +124 -1
- 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 +12 -0
- 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/frame.rb
CHANGED
|
@@ -4,6 +4,12 @@ require "ferrum/frame/dom"
|
|
|
4
4
|
require "ferrum/frame/runtime"
|
|
5
5
|
|
|
6
6
|
module Ferrum
|
|
7
|
+
#
|
|
8
|
+
# Represents a frame (the main document or a nested `iframe`) within a
|
|
9
|
+
# {Page}. Each frame has its own execution context for JavaScript and its
|
|
10
|
+
# own lifecycle state, tracked via {#state}. DOM search and JS evaluation
|
|
11
|
+
# methods are provided by the included {DOM} and {Runtime} modules.
|
|
12
|
+
#
|
|
7
13
|
class Frame
|
|
8
14
|
include DOM
|
|
9
15
|
include Runtime
|
|
@@ -12,6 +18,7 @@ module Ferrum
|
|
|
12
18
|
started_loading
|
|
13
19
|
navigated
|
|
14
20
|
stopped_loading
|
|
21
|
+
canceled
|
|
15
22
|
].freeze
|
|
16
23
|
|
|
17
24
|
# The Frame's unique id.
|
|
@@ -36,16 +43,36 @@ module Ferrum
|
|
|
36
43
|
|
|
37
44
|
# One of the states frame's in.
|
|
38
45
|
#
|
|
39
|
-
# @return [:started_loading, :navigated, :stopped_loading, nil]
|
|
46
|
+
# @return [:started_loading, :navigated, :stopped_loading, :canceled, nil]
|
|
40
47
|
attr_reader :state
|
|
41
48
|
|
|
49
|
+
# Frame loader id.
|
|
50
|
+
#
|
|
51
|
+
# @return [String, nil]
|
|
52
|
+
attr_accessor :loader_id
|
|
53
|
+
|
|
54
|
+
# Frame's lifecycle events (navigation, load, paint, etc.).
|
|
55
|
+
#
|
|
56
|
+
# @return [Array<Hash{String => (String|Float)}>]
|
|
57
|
+
attr_reader :lifecycle_events
|
|
58
|
+
|
|
42
59
|
def initialize(id, page, parent_id = nil)
|
|
43
60
|
@id = id
|
|
44
61
|
@page = page
|
|
45
62
|
@parent_id = parent_id
|
|
63
|
+
@lifecycle_events = []
|
|
46
64
|
@execution_id = Concurrent::MVar.new
|
|
47
65
|
end
|
|
48
66
|
|
|
67
|
+
# Sets the frame's state, validating it's one of {STATE_VALUES}.
|
|
68
|
+
#
|
|
69
|
+
# @param [Symbol] value
|
|
70
|
+
# One of `:started_loading`, `:navigated`, `:stopped_loading`,
|
|
71
|
+
# `:canceled`.
|
|
72
|
+
#
|
|
73
|
+
# @return [Symbol]
|
|
74
|
+
#
|
|
75
|
+
# @raise [ArgumentError]
|
|
49
76
|
def state=(value)
|
|
50
77
|
raise ArgumentError unless STATE_VALUES.include?(value)
|
|
51
78
|
|
|
@@ -94,6 +121,20 @@ module Ferrum
|
|
|
94
121
|
@parent_id.nil?
|
|
95
122
|
end
|
|
96
123
|
|
|
124
|
+
#
|
|
125
|
+
# Returns whether the frame has finished loading (+:stopped_loading+ state).
|
|
126
|
+
# Frames in +:canceled+ state (execution context torn down mid-navigation)
|
|
127
|
+
# are not considered idle.
|
|
128
|
+
#
|
|
129
|
+
# @return [Boolean]
|
|
130
|
+
#
|
|
131
|
+
# @example
|
|
132
|
+
# browser.go_to("https://example.com")
|
|
133
|
+
# browser.main_frame.idle? # => true
|
|
134
|
+
def idle?
|
|
135
|
+
state == :stopped_loading
|
|
136
|
+
end
|
|
137
|
+
|
|
97
138
|
#
|
|
98
139
|
# Returns the parent frame if this frame is nested in another one.
|
|
99
140
|
#
|
|
@@ -164,6 +205,15 @@ module Ferrum
|
|
|
164
205
|
value
|
|
165
206
|
end
|
|
166
207
|
|
|
208
|
+
#
|
|
209
|
+
# Sets the execution context id, or clears it if +nil+ is given (e.g.
|
|
210
|
+
# when the context is torn down mid-navigation and hasn't been
|
|
211
|
+
# replaced yet).
|
|
212
|
+
#
|
|
213
|
+
# @param [Integer, nil] value
|
|
214
|
+
#
|
|
215
|
+
# @return [Integer, nil]
|
|
216
|
+
#
|
|
167
217
|
def execution_id=(value)
|
|
168
218
|
if value.nil?
|
|
169
219
|
@execution_id.try_take!
|
|
@@ -172,11 +222,18 @@ module Ferrum
|
|
|
172
222
|
end
|
|
173
223
|
end
|
|
174
224
|
|
|
225
|
+
#
|
|
226
|
+
# Debug representation of the frame, including its internal state.
|
|
227
|
+
#
|
|
228
|
+
# @return [String]
|
|
229
|
+
#
|
|
175
230
|
def inspect
|
|
176
231
|
"#<#{self.class} " \
|
|
177
232
|
"@id=#{@id.inspect} " \
|
|
178
233
|
"@parent_id=#{@parent_id.inspect} " \
|
|
179
234
|
"@name=#{@name.inspect} " \
|
|
235
|
+
"@loader_id=#{@loader_id.inspect} " \
|
|
236
|
+
"@lifecycle_events=#{@lifecycle_events.inspect} " \
|
|
180
237
|
"@state=#{@state.inspect} " \
|
|
181
238
|
"@execution_id=#{@execution_id.inspect}>"
|
|
182
239
|
end
|
data/lib/ferrum/headers.rb
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Ferrum
|
|
4
|
+
#
|
|
5
|
+
# Manages the extra HTTP headers a page sends with its requests, via the
|
|
6
|
+
# CDP `Network.setExtraHTTPHeaders` command. Setting a `User-Agent` or
|
|
7
|
+
# `Accept-Language` header also updates the corresponding navigator
|
|
8
|
+
# override so JavaScript sees consistent values.
|
|
9
|
+
#
|
|
4
10
|
class Headers
|
|
5
11
|
def initialize(page)
|
|
6
12
|
@page = page
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ferrum
|
|
4
|
+
# Shared by {Page} and {Worker}: subscribes to the CDP events behind the
|
|
5
|
+
# `:request` (Fetch-domain request interception) and `:auth` (proxy/basic
|
|
6
|
+
# auth challenges) pseudo-events, on top of the includer's own `client`
|
|
7
|
+
# and `network`. Anything else is passed straight through to `client`.
|
|
8
|
+
module Interceptable
|
|
9
|
+
# Subscribes to a CDP event, or to `:request`/`:auth`.
|
|
10
|
+
#
|
|
11
|
+
# @param [Symbol, String] name
|
|
12
|
+
#
|
|
13
|
+
# @return [Integer]
|
|
14
|
+
# The subscription id, used to unsubscribe via {#off}.
|
|
15
|
+
def on(name, &block)
|
|
16
|
+
case name
|
|
17
|
+
when :request
|
|
18
|
+
client.on("Fetch.requestPaused") do |params, index, total|
|
|
19
|
+
request = Network::InterceptedRequest.new(client, params)
|
|
20
|
+
exchange = network.find_or_build_exchange(request.network_id)
|
|
21
|
+
exchange.intercepted_request = request
|
|
22
|
+
block.call(request, index, total)
|
|
23
|
+
end
|
|
24
|
+
when :auth
|
|
25
|
+
client.on("Fetch.authRequired") do |params, index, total|
|
|
26
|
+
request = Network::AuthRequest.new(self, params)
|
|
27
|
+
block.call(request, index, total)
|
|
28
|
+
end
|
|
29
|
+
else
|
|
30
|
+
client.on(name, &block)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Unsubscribes a listener previously registered via {#on}.
|
|
35
|
+
#
|
|
36
|
+
# @param [Symbol, String] name
|
|
37
|
+
#
|
|
38
|
+
# @param [Integer] id
|
|
39
|
+
# The subscription id returned by {#on}.
|
|
40
|
+
#
|
|
41
|
+
# @return [void]
|
|
42
|
+
def off(name, id)
|
|
43
|
+
case name
|
|
44
|
+
when :request
|
|
45
|
+
client.off("Fetch.requestPaused", id)
|
|
46
|
+
when :auth
|
|
47
|
+
client.off("Fetch.authRequired", id)
|
|
48
|
+
else
|
|
49
|
+
client.off(name, id)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Whether there's at least one callback registered for the event.
|
|
54
|
+
#
|
|
55
|
+
# @param [String] event
|
|
56
|
+
#
|
|
57
|
+
# @return [Boolean]
|
|
58
|
+
def subscribed?(event)
|
|
59
|
+
client.subscribed?(event)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
data/lib/ferrum/keyboard.rb
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
require "json"
|
|
4
4
|
|
|
5
5
|
module Ferrum
|
|
6
|
+
#
|
|
7
|
+
# Simulates keyboard input for a page via the CDP `Input.dispatchKeyEvent`
|
|
8
|
+
# command. Provides low-level `down`/`up` for individual keys as well as
|
|
9
|
+
# `type` for typing a sequence of characters/keys, translating key names
|
|
10
|
+
# and modifier chords into the CDP key event parameters Chrome expects.
|
|
11
|
+
#
|
|
6
12
|
class Keyboard
|
|
7
13
|
KEYS = JSON.parse(File.read(File.expand_path("keyboard.json", __dir__)))
|
|
8
14
|
MODIFIERS = { "alt" => 1, "ctrl" => 2, "control" => 2,
|
|
@@ -136,6 +142,13 @@ module Ferrum
|
|
|
136
142
|
modifiers: pressed.map { |k| MODIFIERS[k] }.reduce(0, :|)
|
|
137
143
|
)
|
|
138
144
|
|
|
145
|
+
# Synthetic key events alone do not trigger the browser's editing
|
|
146
|
+
# shortcuts (e.g. select-all). When the chord matches a platform
|
|
147
|
+
# accelerator, name the editing command explicitly so Chrome
|
|
148
|
+
# executes it via the CDP `commands` field.
|
|
149
|
+
command = editing_command(pressed, char)
|
|
150
|
+
key = key.merge(commands: [command]) if command
|
|
151
|
+
|
|
139
152
|
modifiers = pressed.map { |k| to_options(KEYS.fetch(KEYS_MAPPING[k.to_sym])) }
|
|
140
153
|
modifiers + [to_options(key)]
|
|
141
154
|
end.flatten
|
|
@@ -157,5 +170,17 @@ module Ferrum
|
|
|
157
170
|
def to_options(hash)
|
|
158
171
|
hash.inject({}) { |memo, (k, v)| memo.merge(k.to_sym => v) }
|
|
159
172
|
end
|
|
173
|
+
|
|
174
|
+
# Names the CDP editing command for a modifier chord, or nil if the chord
|
|
175
|
+
# is not an editing accelerator. The accelerator modifier is Cmd on macOS
|
|
176
|
+
# and Ctrl elsewhere, matching Chrome's own key bindings. Currently only
|
|
177
|
+
# select-all is mapped; extend the guard for further commands as needed.
|
|
178
|
+
def editing_command(pressed, char)
|
|
179
|
+
return unless char.casecmp?("a")
|
|
180
|
+
return if pressed.include?("shift")
|
|
181
|
+
|
|
182
|
+
accelerator = Utils::Platform.mac? ? MODIFIERS["meta"] : MODIFIERS["ctrl"]
|
|
183
|
+
"selectAll" if pressed.map { |k| MODIFIERS[k] }.compact.uniq == [accelerator]
|
|
184
|
+
end
|
|
160
185
|
end
|
|
161
186
|
end
|
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} " \
|