ferrum 0.10.1 → 0.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/README.md +261 -28
- data/lib/ferrum/browser/binary.rb +46 -0
- data/lib/ferrum/browser/client.rb +15 -12
- data/lib/ferrum/browser/command.rb +7 -8
- data/lib/ferrum/browser/options/base.rb +1 -7
- data/lib/ferrum/browser/options/chrome.rb +17 -10
- data/lib/ferrum/browser/options/firefox.rb +11 -4
- data/lib/ferrum/browser/process.rb +41 -35
- data/lib/ferrum/browser/subscriber.rb +1 -3
- data/lib/ferrum/browser/web_socket.rb +9 -12
- data/lib/ferrum/browser/xvfb.rb +4 -8
- data/lib/ferrum/browser.rb +49 -12
- data/lib/ferrum/context.rb +12 -4
- data/lib/ferrum/contexts.rb +13 -9
- data/lib/ferrum/cookies.rb +10 -9
- data/lib/ferrum/errors.rb +115 -0
- data/lib/ferrum/frame/runtime.rb +20 -17
- data/lib/ferrum/frame.rb +32 -24
- data/lib/ferrum/headers.rb +2 -2
- data/lib/ferrum/keyboard.rb +11 -11
- data/lib/ferrum/mouse.rb +8 -7
- data/lib/ferrum/network/auth_request.rb +7 -2
- data/lib/ferrum/network/exchange.rb +14 -10
- data/lib/ferrum/network/intercepted_request.rb +10 -8
- data/lib/ferrum/network/request.rb +5 -0
- data/lib/ferrum/network/response.rb +4 -4
- data/lib/ferrum/network.rb +124 -35
- data/lib/ferrum/node.rb +98 -40
- data/lib/ferrum/page/animation.rb +15 -0
- data/lib/ferrum/page/frames.rb +46 -15
- data/lib/ferrum/page/screenshot.rb +53 -67
- data/lib/ferrum/page/stream.rb +38 -0
- data/lib/ferrum/page/tracing.rb +71 -0
- data/lib/ferrum/page.rb +88 -34
- data/lib/ferrum/proxy.rb +58 -0
- data/lib/ferrum/{rbga.rb → rgba.rb} +4 -2
- data/lib/ferrum/target.rb +1 -0
- data/lib/ferrum/utils/attempt.rb +20 -0
- data/lib/ferrum/utils/elapsed_time.rb +27 -0
- data/lib/ferrum/utils/platform.rb +28 -0
- data/lib/ferrum/version.rb +1 -1
- data/lib/ferrum.rb +4 -140
- metadata +65 -50
data/lib/ferrum/network.rb
CHANGED
@@ -14,6 +14,10 @@ module Ferrum
|
|
14
14
|
RESOURCE_TYPES = %w[Document Stylesheet Image Media Font Script TextTrack
|
15
15
|
XHR Fetch EventSource WebSocket Manifest
|
16
16
|
SignedExchange Ping CSPViolationReport Other].freeze
|
17
|
+
AUTHORIZE_BLOCK_MISSING = "Block is missing, call `authorize(...) { |r| r.continue } " \
|
18
|
+
"or subscribe to `on(:request)` events before calling it"
|
19
|
+
AUTHORIZE_TYPE_WRONG = ":type should be in #{AUTHORIZE_TYPE}"
|
20
|
+
ALLOWED_CONNECTION_TYPE = %w[none cellular2g cellular3g cellular4g bluetooth ethernet wifi wimax other].freeze
|
17
21
|
|
18
22
|
attr_reader :traffic
|
19
23
|
|
@@ -21,13 +25,16 @@ module Ferrum
|
|
21
25
|
@page = page
|
22
26
|
@traffic = []
|
23
27
|
@exchange = nil
|
28
|
+
@blacklist = nil
|
29
|
+
@whitelist = nil
|
24
30
|
end
|
25
31
|
|
26
32
|
def wait_for_idle(connections: 0, duration: 0.05, timeout: @page.browser.timeout)
|
27
|
-
start =
|
33
|
+
start = Utils::ElapsedTime.monotonic_time
|
28
34
|
|
29
35
|
until idle?(connections)
|
30
|
-
raise TimeoutError if
|
36
|
+
raise TimeoutError if Utils::ElapsedTime.timeout?(start, timeout)
|
37
|
+
|
31
38
|
sleep(duration)
|
32
39
|
end
|
33
40
|
end
|
@@ -61,9 +68,7 @@ module Ferrum
|
|
61
68
|
end
|
62
69
|
|
63
70
|
def clear(type)
|
64
|
-
unless CLEAR_TYPE.include?(type)
|
65
|
-
raise ArgumentError, ":type should be in #{CLEAR_TYPE}"
|
66
|
-
end
|
71
|
+
raise ArgumentError, ":type should be in #{CLEAR_TYPE}" unless CLEAR_TYPE.include?(type)
|
67
72
|
|
68
73
|
if type == :traffic
|
69
74
|
@traffic.clear
|
@@ -74,23 +79,28 @@ module Ferrum
|
|
74
79
|
true
|
75
80
|
end
|
76
81
|
|
82
|
+
def blacklist=(patterns)
|
83
|
+
@blacklist = Array(patterns)
|
84
|
+
blacklist_subscribe
|
85
|
+
end
|
86
|
+
alias blocklist= blacklist=
|
87
|
+
|
88
|
+
def whitelist=(patterns)
|
89
|
+
@whitelist = Array(patterns)
|
90
|
+
whitelist_subscribe
|
91
|
+
end
|
92
|
+
alias allowlist= whitelist=
|
93
|
+
|
77
94
|
def intercept(pattern: "*", resource_type: nil)
|
78
95
|
pattern = { urlPattern: pattern }
|
79
|
-
if resource_type && RESOURCE_TYPES.include?(resource_type.to_s)
|
80
|
-
pattern[:resourceType] = resource_type
|
81
|
-
end
|
96
|
+
pattern[:resourceType] = resource_type if resource_type && RESOURCE_TYPES.include?(resource_type.to_s)
|
82
97
|
|
83
98
|
@page.command("Fetch.enable", handleAuthRequests: true, patterns: [pattern])
|
84
99
|
end
|
85
100
|
|
86
101
|
def authorize(user:, password:, type: :server, &block)
|
87
|
-
unless AUTHORIZE_TYPE.include?(type)
|
88
|
-
|
89
|
-
end
|
90
|
-
|
91
|
-
if !block_given? && !@page.subscribed?("Fetch.requestPaused")
|
92
|
-
raise ArgumentError, "Block is missing, call `authorize(...) { |r| r.continue } or subscribe to `on(:request)` events before calling it"
|
93
|
-
end
|
102
|
+
raise ArgumentError, AUTHORIZE_TYPE_WRONG unless AUTHORIZE_TYPE.include?(type)
|
103
|
+
raise ArgumentError, AUTHORIZE_BLOCK_MISSING if !block_given? && !@page.subscribed?("Fetch.requestPaused")
|
94
104
|
|
95
105
|
@authorized_ids ||= {}
|
96
106
|
@authorized_ids[type] ||= []
|
@@ -116,6 +126,53 @@ module Ferrum
|
|
116
126
|
end
|
117
127
|
|
118
128
|
def subscribe
|
129
|
+
subscribe_request_will_be_sent
|
130
|
+
subscribe_response_received
|
131
|
+
subscribe_loading_finished
|
132
|
+
subscribe_loading_failed
|
133
|
+
subscribe_log_entry_added
|
134
|
+
end
|
135
|
+
|
136
|
+
def authorized_response(ids, request_id, username, password)
|
137
|
+
if ids.include?(request_id)
|
138
|
+
{ response: "CancelAuth" }
|
139
|
+
elsif username && password
|
140
|
+
{ response: "ProvideCredentials",
|
141
|
+
username: username,
|
142
|
+
password: password }
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def select(request_id)
|
147
|
+
@traffic.select { |e| e.id == request_id }
|
148
|
+
end
|
149
|
+
|
150
|
+
def build_exchange(id)
|
151
|
+
Network::Exchange.new(@page, id).tap { |e| @traffic << e }
|
152
|
+
end
|
153
|
+
|
154
|
+
def emulate_network_conditions(offline: false, latency: 0,
|
155
|
+
download_throughput: -1, upload_throughput: -1,
|
156
|
+
connection_type: nil)
|
157
|
+
params = {
|
158
|
+
offline: offline, latency: latency,
|
159
|
+
downloadThroughput: download_throughput,
|
160
|
+
uploadThroughput: upload_throughput
|
161
|
+
}
|
162
|
+
|
163
|
+
params[:connectionType] = connection_type if connection_type && ALLOWED_CONNECTION_TYPE.include?(connection_type)
|
164
|
+
|
165
|
+
@page.command("Network.emulateNetworkConditions", **params)
|
166
|
+
true
|
167
|
+
end
|
168
|
+
|
169
|
+
def offline_mode
|
170
|
+
emulate_network_conditions(offline: true, latency: 0, download_throughput: 0, upload_throughput: 0)
|
171
|
+
end
|
172
|
+
|
173
|
+
private
|
174
|
+
|
175
|
+
def subscribe_request_will_be_sent
|
119
176
|
@page.on("Network.requestWillBeSent") do |params|
|
120
177
|
request = Network::Request.new(params)
|
121
178
|
|
@@ -140,25 +197,29 @@ module Ferrum
|
|
140
197
|
|
141
198
|
exchange.request = request
|
142
199
|
|
143
|
-
if exchange.navigation_request?(@page.main_frame.id)
|
144
|
-
@exchange = exchange
|
145
|
-
end
|
200
|
+
@exchange = exchange if exchange.navigation_request?(@page.main_frame.id)
|
146
201
|
end
|
202
|
+
end
|
147
203
|
|
204
|
+
def subscribe_response_received
|
148
205
|
@page.on("Network.responseReceived") do |params|
|
149
|
-
|
206
|
+
exchange = select(params["requestId"]).last
|
207
|
+
|
208
|
+
if exchange
|
150
209
|
response = Network::Response.new(@page, params)
|
151
210
|
exchange.response = response
|
152
211
|
end
|
153
212
|
end
|
213
|
+
end
|
154
214
|
|
215
|
+
def subscribe_loading_finished
|
155
216
|
@page.on("Network.loadingFinished") do |params|
|
156
217
|
exchange = select(params["requestId"]).last
|
157
|
-
|
158
|
-
exchange.response.body_size = params["encodedDataLength"]
|
159
|
-
end
|
218
|
+
exchange.response.body_size = params["encodedDataLength"] if exchange&.response
|
160
219
|
end
|
220
|
+
end
|
161
221
|
|
222
|
+
def subscribe_loading_failed
|
162
223
|
@page.on("Network.loadingFailed") do |params|
|
163
224
|
exchange = select(params["requestId"]).last
|
164
225
|
exchange.error ||= Network::Error.new
|
@@ -169,7 +230,9 @@ module Ferrum
|
|
169
230
|
exchange.error.monotonic_time = params["timestamp"]
|
170
231
|
exchange.error.canceled = params["canceled"]
|
171
232
|
end
|
233
|
+
end
|
172
234
|
|
235
|
+
def subscribe_log_entry_added
|
173
236
|
@page.on("Log.entryAdded") do |params|
|
174
237
|
entry = params["entry"] || {}
|
175
238
|
if entry["source"] == "network" && entry["level"] == "error"
|
@@ -184,24 +247,50 @@ module Ferrum
|
|
184
247
|
end
|
185
248
|
end
|
186
249
|
|
187
|
-
def
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
250
|
+
def blacklist_subscribe
|
251
|
+
return unless blacklist?
|
252
|
+
raise ArgumentError, "You can't use blacklist along with whitelist" if whitelist?
|
253
|
+
|
254
|
+
@blacklist_subscribe ||= begin
|
255
|
+
intercept
|
256
|
+
|
257
|
+
@page.on(:request) do |request|
|
258
|
+
if @blacklist.any? { |p| request.match?(p) }
|
259
|
+
request.abort
|
260
|
+
else
|
261
|
+
request.continue
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
true
|
196
266
|
end
|
197
267
|
end
|
198
268
|
|
199
|
-
def
|
200
|
-
|
269
|
+
def whitelist_subscribe
|
270
|
+
return unless whitelist?
|
271
|
+
raise ArgumentError, "You can't use whitelist along with blacklist" if blacklist?
|
272
|
+
|
273
|
+
@whitelist_subscribe ||= begin
|
274
|
+
intercept
|
275
|
+
|
276
|
+
@page.on(:request) do |request|
|
277
|
+
if @whitelist.any? { |p| request.match?(p) }
|
278
|
+
request.continue
|
279
|
+
else
|
280
|
+
request.abort
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
true
|
285
|
+
end
|
201
286
|
end
|
202
287
|
|
203
|
-
def
|
204
|
-
|
288
|
+
def blacklist?
|
289
|
+
Array(@blacklist).any?
|
290
|
+
end
|
291
|
+
|
292
|
+
def whitelist?
|
293
|
+
Array(@whitelist).any?
|
205
294
|
end
|
206
295
|
end
|
207
296
|
end
|
data/lib/ferrum/node.rb
CHANGED
@@ -2,15 +2,16 @@
|
|
2
2
|
|
3
3
|
module Ferrum
|
4
4
|
class Node
|
5
|
-
|
6
|
-
|
5
|
+
MOVING_WAIT_DELAY = ENV.fetch("FERRUM_NODE_MOVING_WAIT", 0.01).to_f
|
6
|
+
MOVING_WAIT_ATTEMPTS = ENV.fetch("FERRUM_NODE_MOVING_ATTEMPTS", 50).to_i
|
7
7
|
|
8
8
|
attr_reader :page, :target_id, :node_id, :description, :tag_name
|
9
9
|
|
10
10
|
def initialize(frame, target_id, node_id, description)
|
11
11
|
@page = frame.page
|
12
12
|
@target_id = target_id
|
13
|
-
@node_id
|
13
|
+
@node_id = node_id
|
14
|
+
@description = description
|
14
15
|
@tag_name = description["nodeName"].downcase
|
15
16
|
end
|
16
17
|
|
@@ -30,6 +31,27 @@ module Ferrum
|
|
30
31
|
tap { page.command("DOM.focus", slowmoable: true, nodeId: node_id) }
|
31
32
|
end
|
32
33
|
|
34
|
+
def focusable?
|
35
|
+
focus
|
36
|
+
true
|
37
|
+
rescue BrowserError => e
|
38
|
+
e.message == "Element is not focusable" ? false : raise
|
39
|
+
end
|
40
|
+
|
41
|
+
def wait_for_stop_moving(delay: MOVING_WAIT_DELAY, attempts: MOVING_WAIT_ATTEMPTS)
|
42
|
+
Utils::Attempt.with_retry(errors: NodeMovingError, max: attempts, wait: 0) do
|
43
|
+
previous, current = content_quads_with(delay: delay)
|
44
|
+
raise NodeMovingError.new(self, previous, current) if previous != current
|
45
|
+
|
46
|
+
current
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def moving?(delay: MOVING_WAIT_DELAY)
|
51
|
+
previous, current = content_quads_with(delay: delay)
|
52
|
+
previous == current
|
53
|
+
end
|
54
|
+
|
33
55
|
def blur
|
34
56
|
tap { evaluate("this.blur()") }
|
35
57
|
end
|
@@ -102,17 +124,52 @@ module Ferrum
|
|
102
124
|
def property(name)
|
103
125
|
evaluate("this['#{name}']")
|
104
126
|
end
|
127
|
+
alias [] property
|
105
128
|
|
106
129
|
def attribute(name)
|
107
130
|
evaluate("this.getAttribute('#{name}')")
|
108
131
|
end
|
109
132
|
|
133
|
+
def selected
|
134
|
+
function = <<~JS
|
135
|
+
function(element) {
|
136
|
+
if (element.nodeName.toLowerCase() !== 'select') {
|
137
|
+
throw new Error('Element is not a <select> element.');
|
138
|
+
}
|
139
|
+
return Array.from(element).filter(option => option.selected);
|
140
|
+
}
|
141
|
+
JS
|
142
|
+
page.evaluate_func(function, self, on: self)
|
143
|
+
end
|
144
|
+
|
145
|
+
def select(*values, by: :value)
|
146
|
+
tap do
|
147
|
+
function = <<~JS
|
148
|
+
function(element, values, by) {
|
149
|
+
if (element.nodeName.toLowerCase() !== 'select') {
|
150
|
+
throw new Error('Element is not a <select> element.');
|
151
|
+
}
|
152
|
+
const options = Array.from(element.options);
|
153
|
+
element.value = undefined;
|
154
|
+
for (const option of options) {
|
155
|
+
option.selected = values.some((value) => option[by] === value);
|
156
|
+
if (option.selected && !element.multiple) break;
|
157
|
+
}
|
158
|
+
element.dispatchEvent(new Event('input', { bubbles: true }));
|
159
|
+
element.dispatchEvent(new Event('change', { bubbles: true }));
|
160
|
+
}
|
161
|
+
JS
|
162
|
+
page.evaluate_func(function, self, values.flatten, by, on: self)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
110
166
|
def evaluate(expression)
|
111
167
|
page.evaluate_on(node: self, expression: expression)
|
112
168
|
end
|
113
169
|
|
114
170
|
def ==(other)
|
115
171
|
return false unless other.is_a?(Node)
|
172
|
+
|
116
173
|
# We compare backendNodeId because once nodeId is sent to frontend backend
|
117
174
|
# never returns same nodeId sending 0. In other words frontend is
|
118
175
|
# responsible for keeping track of node ids.
|
@@ -124,44 +181,45 @@ module Ferrum
|
|
124
181
|
end
|
125
182
|
|
126
183
|
def find_position(x: nil, y: nil, position: :top)
|
127
|
-
|
128
|
-
|
129
|
-
# FIXME: Case when a few quads returned
|
130
|
-
points = Ferrum.with_attempts(errors: NodeIsMovingError, max: MOVING_ATTEMPTS, wait: 0) do
|
131
|
-
sleep(MOVING_WAIT)
|
132
|
-
current = get_content_quads
|
133
|
-
|
134
|
-
if current != prev
|
135
|
-
error = NodeIsMovingError.new(self, prev, current)
|
136
|
-
prev = current
|
137
|
-
raise(error)
|
138
|
-
end
|
139
|
-
|
140
|
-
current
|
141
|
-
end.map { |q| to_points(q) }.first
|
142
|
-
|
184
|
+
points = wait_for_stop_moving.map { |q| to_points(q) }.first
|
143
185
|
get_position(points, x, y, position)
|
144
|
-
rescue
|
145
|
-
|
186
|
+
rescue CoordinatesNotFoundError
|
187
|
+
x, y = bounding_rect_coordinates
|
188
|
+
raise if x.zero? && y.zero?
|
146
189
|
|
147
|
-
|
190
|
+
[x, y]
|
191
|
+
end
|
192
|
+
|
193
|
+
# Returns a hash of the computed styles for the node
|
194
|
+
def computed_style
|
195
|
+
page
|
196
|
+
.command("CSS.getComputedStyleForNode", nodeId: node_id)["computedStyle"]
|
197
|
+
.each_with_object({}) { |style, memo| memo.merge!(style["name"] => style["value"]) }
|
148
198
|
end
|
149
199
|
|
150
200
|
private
|
151
201
|
|
152
|
-
def
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
202
|
+
def bounding_rect_coordinates
|
203
|
+
evaluate <<~JS
|
204
|
+
[this.getBoundingClientRect().left + window.pageXOffset + (this.offsetWidth / 2),
|
205
|
+
this.getBoundingClientRect().top + window.pageYOffset + (this.offsetHeight / 2)]
|
206
|
+
JS
|
157
207
|
end
|
158
208
|
|
159
|
-
def
|
209
|
+
def content_quads
|
160
210
|
quads = page.command("DOM.getContentQuads", nodeId: node_id)["quads"]
|
161
|
-
raise "Node is either not visible or not an HTMLElement" if quads.size
|
211
|
+
raise CoordinatesNotFoundError, "Node is either not visible or not an HTMLElement" if quads.size.zero?
|
212
|
+
|
162
213
|
quads
|
163
214
|
end
|
164
215
|
|
216
|
+
def content_quads_with(delay: MOVING_WAIT_DELAY)
|
217
|
+
previous = content_quads
|
218
|
+
sleep(delay)
|
219
|
+
current = content_quads
|
220
|
+
[previous, current]
|
221
|
+
end
|
222
|
+
|
165
223
|
def get_position(points, offset_x, offset_y, position)
|
166
224
|
x = y = nil
|
167
225
|
|
@@ -170,28 +228,28 @@ module Ferrum
|
|
170
228
|
x = point[:x] + offset_x.to_i
|
171
229
|
y = point[:y] + offset_y.to_i
|
172
230
|
else
|
173
|
-
x, y = points.inject([0, 0]) do |memo,
|
174
|
-
[memo[0] +
|
175
|
-
memo[1] +
|
231
|
+
x, y = points.inject([0, 0]) do |memo, coordinate|
|
232
|
+
[memo[0] + coordinate[:x],
|
233
|
+
memo[1] + coordinate[:y]]
|
176
234
|
end
|
177
235
|
|
178
|
-
x
|
179
|
-
y
|
236
|
+
x /= 4
|
237
|
+
y /= 4
|
180
238
|
end
|
181
239
|
|
182
240
|
if offset_x && offset_y && position == :center
|
183
|
-
x
|
184
|
-
y
|
241
|
+
x += offset_x.to_i
|
242
|
+
y += offset_y.to_i
|
185
243
|
end
|
186
244
|
|
187
245
|
[x, y]
|
188
246
|
end
|
189
247
|
|
190
248
|
def to_points(quad)
|
191
|
-
[{x: quad[0], y: quad[1]},
|
192
|
-
{x: quad[2], y: quad[3]},
|
193
|
-
{x: quad[4], y: quad[5]},
|
194
|
-
{x: quad[6], y: quad[7]}]
|
249
|
+
[{ x: quad[0], y: quad[1] },
|
250
|
+
{ x: quad[2], y: quad[3] },
|
251
|
+
{ x: quad[4], y: quad[5] },
|
252
|
+
{ x: quad[6], y: quad[7] }]
|
195
253
|
end
|
196
254
|
end
|
197
255
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ferrum
|
4
|
+
class Page
|
5
|
+
module Animation
|
6
|
+
def playback_rate
|
7
|
+
command("Animation.getPlaybackRate")["playbackRate"]
|
8
|
+
end
|
9
|
+
|
10
|
+
def playback_rate=(value)
|
11
|
+
command("Animation.setPlaybackRate", playbackRate: value)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/ferrum/page/frames.rb
CHANGED
@@ -11,35 +11,60 @@ module Ferrum
|
|
11
11
|
@frames.values
|
12
12
|
end
|
13
13
|
|
14
|
-
def frame_by(id: nil, name: nil)
|
14
|
+
def frame_by(id: nil, name: nil, execution_id: nil)
|
15
15
|
if id
|
16
16
|
@frames[id]
|
17
17
|
elsif name
|
18
18
|
frames.find { |f| f.name == name }
|
19
|
+
elsif execution_id
|
20
|
+
frames.find { |f| f.execution_id == execution_id }
|
19
21
|
else
|
20
22
|
raise ArgumentError
|
21
23
|
end
|
22
24
|
end
|
23
25
|
|
24
26
|
def frames_subscribe
|
27
|
+
subscribe_frame_attached
|
28
|
+
subscribe_frame_started_loading
|
29
|
+
subscribe_frame_navigated
|
30
|
+
subscribe_frame_stopped_loading
|
31
|
+
|
32
|
+
subscribe_navigated_within_document
|
33
|
+
|
34
|
+
subscribe_request_will_be_sent
|
35
|
+
|
36
|
+
subscribe_execution_context_created
|
37
|
+
subscribe_execution_context_destroyed
|
38
|
+
subscribe_execution_contexts_cleared
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def subscribe_frame_attached
|
25
44
|
on("Page.frameAttached") do |params|
|
26
45
|
parent_frame_id, frame_id = params.values_at("parentFrameId", "frameId")
|
27
46
|
@frames[frame_id] = Frame.new(frame_id, self, parent_frame_id)
|
28
47
|
end
|
48
|
+
end
|
29
49
|
|
50
|
+
def subscribe_frame_started_loading
|
30
51
|
on("Page.frameStartedLoading") do |params|
|
31
52
|
frame = @frames[params["frameId"]]
|
32
53
|
frame.state = :started_loading
|
33
54
|
@event.reset
|
34
55
|
end
|
56
|
+
end
|
35
57
|
|
58
|
+
def subscribe_frame_navigated
|
36
59
|
on("Page.frameNavigated") do |params|
|
37
60
|
frame_id, name = params["frame"]&.values_at("id", "name")
|
38
61
|
frame = @frames[frame_id]
|
39
62
|
frame.state = :navigated
|
40
63
|
frame.name = name unless name.to_s.empty?
|
41
64
|
end
|
65
|
+
end
|
42
66
|
|
67
|
+
def subscribe_frame_stopped_loading
|
43
68
|
on("Page.frameStoppedLoading") do |params|
|
44
69
|
# `DOM.performSearch` doesn't work without getting #document node first.
|
45
70
|
# It returns node with nodeId 1 and nodeType 9 from which descend the
|
@@ -47,7 +72,7 @@ module Ferrum
|
|
47
72
|
# node will change the id and all subsequent nodes have to change id too.
|
48
73
|
if @main_frame.id == params["frameId"]
|
49
74
|
@event.set if idling?
|
50
|
-
|
75
|
+
document_node_id
|
51
76
|
end
|
52
77
|
|
53
78
|
frame = @frames[params["frameId"]]
|
@@ -55,21 +80,25 @@ module Ferrum
|
|
55
80
|
|
56
81
|
@event.set if idling?
|
57
82
|
end
|
83
|
+
end
|
58
84
|
|
85
|
+
def subscribe_navigated_within_document
|
59
86
|
on("Page.navigatedWithinDocument") do
|
60
87
|
@event.set if idling?
|
61
88
|
end
|
89
|
+
end
|
62
90
|
|
91
|
+
def subscribe_request_will_be_sent
|
63
92
|
on("Network.requestWillBeSent") do |params|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
@event.reset if params["type"] == "Document"
|
70
|
-
end
|
93
|
+
# Possible types:
|
94
|
+
# Document, Stylesheet, Image, Media, Font, Script, TextTrack, XHR,
|
95
|
+
# Fetch, EventSource, WebSocket, Manifest, SignedExchange, Ping,
|
96
|
+
# CSPViolationReport, Other
|
97
|
+
@event.reset if params["frameId"] == @main_frame.id && params["type"] == "Document"
|
71
98
|
end
|
99
|
+
end
|
72
100
|
|
101
|
+
def subscribe_execution_context_created
|
73
102
|
on("Runtime.executionContextCreated") do |params|
|
74
103
|
context_id = params.dig("context", "id")
|
75
104
|
frame_id = params.dig("context", "auxData", "frameId")
|
@@ -83,25 +112,27 @@ module Ferrum
|
|
83
112
|
end
|
84
113
|
|
85
114
|
frame = @frames[frame_id] || Frame.new(frame_id, self)
|
86
|
-
frame.
|
115
|
+
frame.execution_id = context_id
|
87
116
|
|
88
117
|
@frames[frame_id] ||= frame
|
89
118
|
end
|
119
|
+
end
|
90
120
|
|
121
|
+
def subscribe_execution_context_destroyed
|
91
122
|
on("Runtime.executionContextDestroyed") do |params|
|
92
123
|
execution_id = params["executionContextId"]
|
93
|
-
frame =
|
94
|
-
frame
|
124
|
+
frame = frame_by(execution_id: execution_id)
|
125
|
+
frame&.execution_id = nil
|
95
126
|
end
|
127
|
+
end
|
96
128
|
|
129
|
+
def subscribe_execution_contexts_cleared
|
97
130
|
on("Runtime.executionContextsCleared") do
|
98
131
|
@frames.delete_if { |_, f| !f.main? }
|
99
|
-
@main_frame.
|
132
|
+
@main_frame.execution_id = nil
|
100
133
|
end
|
101
134
|
end
|
102
135
|
|
103
|
-
private
|
104
|
-
|
105
136
|
def idling?
|
106
137
|
@frames.all? { |_, f| f.state == :stopped_loading }
|
107
138
|
end
|